> I just don't see how monadic IO is relevant to this issue
Monad is effectively Haskell's definition of time: `A >> B` means A followed by B, `B >> A` means B followed by A. Yet Monad can't express concurrency; for that we need something else (e.g. Arrow).
> putStrLn either acquires a lock or it doesn't
It's not that simple: there's no point aquiring a lock after the text has been written; or relinquishing a lock before it's been aquired. To specify what we want, we need some way to sequence actions, and that's exactly what Monad is, i.e. we want `putStrLn` to be:
getLock >> writeTheString >> freeLock
Then we need to consider the sequencing that's going on in the middle, since we need the first character of our string (e.g. "foo") to appear before the second, and so on. Hence we actually get:
getLock >> putChar 'f' >> putChar 'o' >> putChar 'o' >> putChar '\n' >> freeLock
Hence we need Monad (or at least the definition of >> for IO) to orchestrate a
single sequence of actions. I find it a little unsatisfying that to orchestrate
multiple sequences we might just defer to some complicated parts of the runtime system like "locks" and "threads", written in C :(
As an alternative, we might imagine an "execute concurrently" combinator like `<&>` to complement `>>`, where `A <&> B` evaluates non-deterministically to either `A >> B` or `B >> A`. Yet this isn't particularly good, since `(A >> B) <&> (C >> D)` can only evaluate to either `A >> B >> C >> D` or `C >> D >> A >> B`; there's no possibility for traces like `A >> C >> B >> D`, so we lose a lot of the reason to use concurrency.
One way to fix this is to define another way of sequencing, e.g. `A ->> B`, which behaves like `A >> B`. We can then pick one of these, e.g. `->>`, to interact with `<&>`, such that:
(A ->> B) <&> (C ->> D)
will evalutate non-deterministically to:
A >> (B <&> (C ->> D))
or:
C >> ((A ->> B) <&> D)
With a setup like this, we can then reframe the problem with `putStrLn` by saying that `putStrLn "foo"` evaluates to:
putChar 'f' ->> putChar 'o' ->> putChar 'o' ->> putChar '\n'
Whilst we want it to evaluate to:
putChar 'f' >> putChar 'o' >> putChar 'o' >> putChar '\n'
From this perspective, the problem is absolutely about monadic IO :)
Can we implement `<&>` with threads and `>>` with locks? Sure; but we can also think about these things in a "more Haskelly" way, with combinators, equational laws, etc.