Others like Clojure and Scala have more support at the standard library level.
Others like Clojure and Scala have more support at the standard library level.
I don't understand how. The only difference I see is whether the interprocess communication parts are coded into the language itself or whether they're coded into a library. It could theoretically make the problem more difficult to put it in the language itself. Instead of only affecting the programs that use that library, now your concurrency features could potentially affect any program written in your language.
Could you give me an example of a concurrency feature that really, truly benefits from being in the language and not the standard library? I simply can't think of any examples.
Isn't that a bit of a loaded question? In some academic sense, an ideal programming system might have a kernel language that is probably rather small and certainly clean, flexible and extensible in its feature set, and then almost everything else built on top of that kernel in libraries. In practical programming systems, the perfect kernel has proven rather elusive, though.
It's rather like the question of whether a standard library should be small, clean and extensible or comprehensive with a good-enough version of everything. Many of us might be inclined toward the former approach from an academic/theoretical/intuitive point of view, but in practice, just about every widely used programming language from the past two decades has come with a batteries-included library. While those libraries are often criticised for various technical weaknesses, and many of those weaknesses are never addressed because it's too much work, these systems are still good enough for most users out of the box.
Erlang has that, because it has its datatypes and a defined serialization for them and there is nothing in the language that is not those datatypes. It also doesn't permit you to layer any type-level assertions about those types into a user type. In fact, Erlang basically has no concept of user types. (Records are syntax sugar around the built-in tuple.) Since the Erlang data types are so weak, an automated serialization can be built that requires no work to use. But that doesn't come without cost
If you don't start with that, you have to use some sort of introspection to examine data types, and you probably don't have a good story for what to do if two ends have totally different ideas about those datatypes, or how to upgrade running processes where you literally want to change the datatype without shutting the process down. The stronger your type system, the harder that gets. The easier it is, the weaker your type system must be. Erlang's capabilities don't come for free, they exist because they wrote their (non-)type system so that their data structures never have any guarantees in them that don't trivially travel across the network. This has its own issues; Erlang has just about the weakest types you can have without actually having arbitrary casts, and that has consequences too.
There are a handful of characteristics in a language, like its type system, that have radical impacts throughout the language and no amount of library jiggery-pokery can completely paper over. Another example not entirely relevant to cross-process concurrency (but not entirely irrelevant) is immutability; no amount of library work can turn a mutable language into an immutable language.
I've been attempting to work with GWT and the serialization issues are entertaining (did you implement IsSerializable? is there a no-arg constructor? Do all of the classes your class depend on have the same? Did you remember to recompile the GWT/JS code to update the serialization white list? etc.).
It makes me appreciate more playing with Erlang a while back and shooting data around being so simple.
A bit further discussion: http://news.ycombinator.com/item?id=939364
Not to detract from anything you said (which I agree with), but when Guy Steele gave a guest lecture at my university on Fortress a few months ago, he said that the work splitting algorithm still needed work, in particular the part that decided the right amount of granularity for the given task (i.e. when to stop splitting the task into smaller subtasks).
Clojure has deep language support for concurrency - from syntactical constructs to it's core performant immutable data structures.
The only exception is the deref reader macro "@", since Clojure doesn't allow user-defined reader macros by default.
This is not true at present. The core data structures, and the STM machinery are implemented in java, not as macros.
And a whole pile of others.
Did you use a parallel programming language?
We've gotten comfortable enough with our concurrency model, that we felt it deserved to be in the language.
As an aside, some features that benefit parallelism can be tricky to add retroactively, although possible (like an N:M threading model).
Does Spin address this issue?
On p.173 Joe writes:
When we say things such as this:
Pid = register(...),
on_exit(Pid, fun(X) -> ..),
there is a possibility the process dies in the gap between these two statements.
Regarding process death, we allow multiple inter-process relationships to be specified at spawn time. Hence any number of processes may be configured to observe a newly-spawned process before it has even started. Additionally, all children are linked to (die with) their parent, which both eliminates the risk of orphaned processes and has other benefits.So in Spin you wouldn't be tempted to write something like above (probably not Erlang either?).