4 ms·
Would you mind elaborating on "why separate address spaces connected by pipes are such a powerful abstraction", or point me to some sources? Likewise if there's
by Joe-Z 8y ago
Would you mind elaborating on "why separate address spaces connected by pipes are such a powerful abstraction", or point me to some sources? Likewise if there's something you can recommend for reading up on the Actor model?
- theoh 8y agoThe phrase probably refers to Unix pipes: https://wiki.tuhs.org/doku.php?id=features:pipes https://wiki.tuhs.org/doku.php?id=features:pipes
- zackmorris 8y agoYa in my mind, executables that do one thing well and are connected by pipes is one of the most proven models for getting things done. Even relatively nontechnical people can write shell scripts, batch files and macros that take some kind of data from a socket/file/pipe, pass it through a bunch of black boxes and spit out an answer. This is very similar to the Actor model and is much simpler to reason about than the shared state threads of languages like Java. Optimizations like copy on write can eliminate most of the overhead of sending data between processes. Here's a quick overview of how the Actor model is better than shared state (mainly by avoiding locks and nondeterminism): https://doc.akka.io/docs/akka/2.5.3/scala/guide/actors-intro.html https://doc.akka.io/docs/akka/2.5.3/scala/guide/actors-intro... And just as an aside, I haven't fully learned Rust yet but from what I understand, the borrow checker only applies to mutable data. If you write a fully immutable Rust program then you can avoid it altogether. So to me, this is a potential bridge between functional and imperative programming. It also might apply to monads, since (from what I understand) languages like ClojureScript can run in one-shot mode where all side effect-free code runs completely and then the Javascript runtime suspends execution, only starting it again once new data is ready. This might also be a bridge between functional and imperative code, because I've found monads to be one of the weakest links in functional programming and I've never quite wrapped my head around how they work or if they're even a good abstraction. Maybe someone can elaborate on them! In short, the goal here is to reduce all concurrent code to a single thread of execution that is statically analyzable as much as possible. So a typical C++ desktop app may not see much benefit from this, but anyone who has found themselves in Javascript callback hell can certainly appreciate the advantages that async/await provides, since it more closely approximates the message passing of Erlang/Go, which is more similar to shell scripting. If we had a "perfect" language that optimized all code to be concurrent as much as possible, for example by converting for-loops into higher order functions like map and then running them on separate threads behind the scenes, then we could offload the wasted work currently done by humans onto the runtime. So that would mean that instead of writing programs that, say, access remote APIs, cache the data somehow, remember to invalidate it when the source of truth changes, etc etc etc, that complexity instead could be reduced to a dependency graph that works like a spreadsheet and updates all dependencies when something changes. This seems to be where the world is going with reactive programming, with a lot of handwaving and ugly syntax because it's reimplementing old concepts from Lisp etc: https://en.wikipedia.org/wiki/Reactive_programming https://en.wikipedia.org/wiki/Reactive_programming Sorry this got a little long, I could ramble about the downsides of programming in these times for hours.
- steveklabnik 8y ago> I haven't fully learned Rust yet but from what I understand, the borrow checker only applies to mutable data This is not correct. The borrow checker applies to references, both mutable and immutable.
- drchickensalad 8y agoI think the intention was that if you have no mut refs, then you have no borrow checker problems.
- steveklabnik 8y agoThat’s not true. You still have things like dangling pointers being checked for.
- zackmorris 8y agoOk thanks for the followup, it was something I heard but hadn't tried in practice.
- steveklabnik 8y agoNo problem!
- Joe-Z 8y agoThank you for the follow-up and the links! I wonder if your suggestion would hold if it were put to the test and one were to implement a big and complex software strictly with the independent processes+pipes model. I could well see that you would wind up in some other sort of hell, because the problem of understanding lies not in the way it's implemented but in the complexity of the tasks involved. >Sorry this got a little long, I could ramble about the downsides of programming in these times for hours. Haha, programmer talk is the best!
- aidenn0 8y agoCSP[1] is also related. For a quick breakdown of why CSP and the Actor model are both Good Ideas: Think about any concurrent system (i.e. multiple threads of execution). Any modification of a shared resource by one thread (one concrete example is a global variable, but filesystem, devices &c. all apply too), is implicit communication that can happen at any point whatsoever in another threads execution. The state of one thread being modified in unsynchronized ways in another inevitably leads to bugs that are very hard to reason about. Both CSP and Actors involve removing the implicit communication and replacing it with explicit communication. Two Unix processes connected by pipes are one example of this, since two processes do not typically share any writable memory. I don't know if you've written any code designed to be used over a pipe. If you have, you may notice that you do not care at all when the process on the other side of a pipe writes to variables. You do not need locks or mutexes or semaphores or any of those constructs for IPC in this situation. I don't know if you've ever written any multithreaded code with shared variables, but if you have, you've definitely noticed that you need to carefully hand-synchronize modifications to those variables. Now of course, the only special thing about the unix processes example was the lack of mutable shared state, and the use of an explicit communication channel. You could write a single-process with many threads that communicate over such channels to avoid the IPC overhead of unix, but still get the simple-to-reason-about concurrency of processes. Pretty much all of the ideas above was published before 1980. My dad read Hoare's CSP paper when getting his Masters degree and he told me "80% of the class didn't understand it, and the remaining 20% of the class thought that manual synchronization was not a problem" which explains much of the buggy, highly concurrent software written since. 1: https://en.wikipedia.org/wiki/Communicating_sequential_processes https://en.wikipedia.org/wiki/Communicating_sequential_proce...
- Joe-Z 8y agoThank you for the insight. What you're describing reminds me pretty strongly of pure functions. Which I guess makes sense, since "Functional languages can do concurrency out of the box!!" (well yeah, but you need to learn how to write truly functional code first :D) >"80% of the class didn't understand it, and the remaining 20% of the class thought that manual synchronization was not a problem" Unfortunately it seems to be this way with many things in our field (/life?). And the 80% non-understanders group doesn't even stay the same people for every topic! :)