Phrased like this, it sounds like 'the monad abstraction' and 'good syntax for monads' are what helps, which is slightly off the mark.
But particular monads do help:
- The Par monad gives you deterministic parallelism for computations.
- The flipside of marking mutable/non-determistic functions as IO (another monad), is that the functions not marked as such are deterministic, non-interfering etc., so interleaving them differently will not grow the state-space.
- The STM monad gives you transactions, which give you safe, shared mutability without running into the following issue from the article:
> I can use programming constructs like mutexes and barriers to "prune" the state space and give me the behaviors I want, but given how big the state space can be, I have to do a lot of pruning to get the right behaviors. I can make mistakes in implementation, "misshape" the space (like by adding a deadlock), or not notice a buggy state I need to remove. Threads are very error prone.
I'm not sure what 'Atoms' refer to, unless it's running code in an atomic block in STM.
That said, having a good monadic syntax is still great, even if the syntax can't solve semantic concurrency issues. I believe that CompletableFutures were a good way to model async code in Java (and Rx/observables etc, to some extent). But they got resistance from Java programmers who didn't want to flatMap everything all the time. They had semantic issues too (can't cancel?!? what?!). But I think it's largely their syntax that made programmers miss the Thread model of writing straight-line imperative code.
You'll also come across them in Lisp-like languages and logic programming.
Edit: Another, maybe better, illustration from the BEAM might be the 'booleans', they're implemented as the atoms :true and :false.
> But particular monads do help:
The part the language needs to provide are merely the abstraction and the good syntax which uses it, not the particular monads: you can then implement them as required.