Comment by nh2
11 hours ago
I haven't found a Go concurrency thing yet that hasn't long-existed in Haskell before.
I also find Haskell's concurrency in practice much easier to reason about than Go's, let me do a pitch:
In Haskell you can just fork a thread and block till it's done. Threaded, "async" logic just looks like blocking serial code (but isn't blocking). I feel like in typical channel-based Go code I have to jump and scroll a lot in the code because of all the message-passing instead of block-scoped "blocking-style" variable use, and that this makes it hard to conclude whether the whole thing terminates or deadlocks.
In Haskell, channels are considered low-level concurrency primitives you should only use when you have no clean high-level primitives for it. This is because they are not "structured" concurrency: When you send something into a channel, it is gone out of your scope, and you now need to track in your brain where it is, and who should consume that thing in the right way ("message-passing").
For example, in Stolon, a high-availability Postgres orchestrator written in Go (https://github.com/sorintlab/stolon), I found the logic for failover with multiple channels and various timeouts very difficult to reason about when investigating failover bugs. I'm pretty sure that would read much easier in Haskell (see below how).
In Haskell, you can start 2, or N, things in parallel, and easily wait till they are done. You can invoke parallel `map` easily.
results <- mapConcurrently f mylist
If f throws on any element, the whole map throws, and other threads get cancelled automatically as expected.
You can get bounded, steaming parallelism, easily.
You can set time limits to function calls writing
timeout 1000 (myIoFunction ...)
You can cancel any thread or computation, at any time. The same timeout function can cancel blocking IO operations, such as reading from the terminal or sockets, without having pass around `Context` objects like in Go (which, if you forget it, just makes things hang or deadlock).
You wrap the 2 words "timeout 1000" around your function and done.
Concurrency _composes_ in Haskell. You can write
res :: Maybe (Maybe a) < timeout a (timeout b (myIoFunction ...))
and the returned type tells you cleanly at which level the cancellation occured (no mixing into the same `error` type.
You can build trees of parallel operations that live and die together.
And there are no data races (because mutability is a very explicit thing), and I'm not even mentioning STM here (which allows you to do database-style transactions across variables) because that's already pointed out in another post.
As a composed example, in Haskell you can write:
timeout 1000 (race (downloadUrl ...) (forever (putStrLn "Still loading ...")))
and that will do exactly what you think it should, with correct Ctrl+C cancellability, and good developer ergonomics.
If you enjoy concurrency, give Haskell a shot!
No comments yet
Contribute on Hacker News ↗