I've used Rails, Node.js and .NET before this, and none of them came close to Elixir in terms of developer productivity, not to mention system stability.
How much of this is due to (the minority of) devs that can code Erlang/Elixir being more experienced/productive regardless of the language? Or just having good tech management?
Let me give you an example. I have an actor that asks for two objects from a DbActor, and then sends them to the AdderActor to get the sum. Without preemption, I have to do the following.
1. Send a message to get object A and object B. Return.
2. Wait for an object.
3. When I get the object, figure out if it is object A or B (or maybe it's something else entirely). Update internal state so that I know I need one more object. Return.
4. Wait for an object
5. Get the object, figure out if it is object A or B (or something else). Update internal state so I know that I have both objects. Send the two objects and my Add message to the AdderActor. Return.
6. Wait for the result.
7. Now that I've gotten the result, update internal state saying that I've completed my A+B operation. Either store the result of A+B or whatever.
This method requires a lot of internal booking and a lot of mutable hash tables in order to keep track of all the various stuff this actor is in the process of doing. If I'm trying to calculate A+B and C+D, I need to know how far along I am in each computation. This is a buggy and annoying process that creates a ton of complexity.
Let's look at how we could do it in Erlang:
1. Send a message saying we want object A.
2. Don't return, and instead block until we get A. Now we ask for B.
3. Block until we get B. Now send the A+B message.
4. Block until we get it. Do something. Return.
See how much easier this is? Because we can block, our code actually can be structured to look like normal, non-concurrent code. It can be executed in a sequential fashion, even though the actual computation is happening somewhere else. With Akka and Akka.net, we can't do that. We have to return as quickly as possible, or else we are going to lock up the entire system. This means that all of the implicit state that a normal program has (like current line being executed), must be stored explicitly by our program.
It sucks, but hopefully the JVM will get preemption sometime soon. The actor model doesn't really work with out it. And even though it doesn't work without it, it's still better than threads and mutexes.
https://getakka.net/articles/concepts/actor-systems.html#blo...
Coroutines in general are a form of cooperative multi-tasking (they must explicitly yield), but I might missing something about .NET's implementation.
> Examples are legacy RDBMS drivers or messaging APIs, and the underlying reason is typically that (network) I/O occurs under the covers
The issue with async/await is that APIs need to be extended with their async counterparts. If they don't, then you have to be sure not to invoke blocking calls. Java won't have this issue as the runtime will take care of preemption.
for {
a <- aProviderActor ? gimmeA
b <- bProviderActor ? gimmeB
aPlusB <- adderActor ? (add, a, b)
} yield aPlusB