3 ms·
Haskell seems to be the only language where throwing exceptions at another thread isn't a box of footguns. Anyone know why that is?
by endgame 2y ago
Haskell seems to be the only language where throwing exceptions at another thread isn't a box of footguns. Anyone know why that is?
- WJW 2y agoI don't know why you think that it isn't. You can do so, but AFAIK almost no libraries are written with the expectation that their computations might be interrupted at any time by another thread. The best you can hope for is that you "only" interrupt some pure computation and not throw your exception into the middle of some delicately sequenced IO side effects.
- whateveracct 2y ago> AFAIK almost no libraries are written with the expectation that their computations might be interrupted at any time by another thread. Haskell libraries are written this way all the time. Exception-safe libraries with async-aware handling are pretty commonplace nowadays (partially thanks to safe-exceptions and unsafeio providing a nice API to deal with them.)
- kccqzy 2y agoAbsolutely false. At least not in my experience. Only novice Haskell programmers write code that can't handle computations being interrupted by another thread. A published and maintained Haskell library is unlikely to have these issues.
- masklinn 2y agoErlang / Elixir is almost above that: - the shared-nothing structuring means it’s harder to corrupt external - that processes can die at any moment is part of the culture - the supervision tree means the most likely case for killing an other process is a parent killing a misbehaving child, which they can then handle as if the child had faulted
- jerf 2y agoAnother key to Erlang's success here is the ports system. Basically "external" resources have a defined cleanup mechanism even if the process that created them dies. An open socket under the hood has its own thread it can use to clean up. With effort you can do this with your own things too through the linking mechanism, having some process watch another process using your resources and then have an execution thread that can clean up if the user spontaneously dies. Some of it too is just Erlang being too, err, for lack of a better word "weak" to have some of the problematic resources. Since Erlang has no "locks", you simply can't take a lock and then kill the thread responsible for unlocking it. You can construct such locks at a higher level, but Erlang's design tends to encourage other designs. For instance, if I were designing a higher-level lock, I'd design it to link to the process taking the lock and release it if it dies. This can still theoretically get you into trouble but especially if this is documented as the semantics of the lock, you have to work a lot harder at it. If you really worked at it you could manage to screw up your Erlang system with thread killing through your own lock implementation and such, but it still will be something you can recover from the REPL, you won't confuse BEAM itself. In an imperative language you'd be ranging from lucky to be able to reach in somehow and fix it to there simply being no way.
- nitros 2y agobracket [1] is easy to use and ensures that the cleanup operation runs, the Control.Exception module is also well documented [2] [1]: https://hackage.haskell.org/package/base-4.20.0.1/docs/Control-Exception.html#v:bracket https://hackage.haskell.org/package/base-4.20.0.1/docs/Contr... [2]: https://hackage.haskell.org/package/base-4.20.0.1/docs/Control-Exception.html https://hackage.haskell.org/package/base-4.20.0.1/docs/Contr...
- whateveracct 2y agoNot to mention the widespread safe-exceptions and unliftio provide an API that makes it hard to shoot yourself in the foot. Pretty funny that Haskell of all languages also has best-in-class exceptions & exception handling haha.
- valcron1000 2y ago"The finest imperative programming language"
- whateveracct 2y agoIt has good exception libraries that differentiate synchronous vs asynchronous exceptions. It also has good learning material on the subject in the form of thorough blog posts. It has robust, types abstractions for both throwing, catching, and masking exceptions. Its exception runtime implementation is better built too.
- kccqzy 2y agoWhile I agree in the aggregate, I do want to mention that Haskell code written by novices is absolutely not correct in the face of asynchronous exceptions. Novice people write `hGetContents h <* hClose h` which obviously leaks file descriptors in the face of both synchronous and asynchronous exceptions. There are also plenty of footguns when you attempt to catch exceptions. A piece of code might intend to catch all synchronous exceptions (raised in the current thread) but accidentally catches asynchronous exceptions. Again this is a novice issue. When that happens, cancelling a thread doesn't work because thread cancellation is implemented with async exceptions. What makes things good is that firstly pure code can only raise exceptions but not catch them. (If you need error handling in pure code, you use the Either monad not exceptions.) This dramatically reduces chances of coding mistakes because exceptions aren't as overused as some other languages. Secondly the Haskell community more or less shuns on direct use of these low-level exception handling APIs and everyone uses the async library https://hackage.haskell.org/package/async-2.2.5/docs/Control-Concurrent-Async.html https://hackage.haskell.org/package/async-2.2.5/docs/Control... which solves not only this problem but also provides nice abstractions to build concurrent computations. Check out its Concurrently type: you now have an Applicative that represents concurrent computations which reuse all your intuition from all Applicative instances. You don't even need to manually create threads or kill threads. Overall insofar as Haskell being the only language where throwing exceptions at another thread isn't a box of footguns, it's only because the language allows a sufficient amount of abstraction power that frees the programmer from using the error-prone low-level APIs. The actual low-level APIs are still full of footguns.
- jwlato 2y agoAbsolutely this. And when you look through the bug history of those low-level APIs there's a lot of evidence. That said, the other big difference with Haskell is the low-level API actually provides functionality to solve the exact problem of an async exception being raised anywhere via `mask`. It's still hard to use correctly but at least it's possible.
- valcron1000 2y agoAsync is a bless. So many language that claim to be "great at concurrency" don't even provide 10% of the async API.
- valcron1000 2y agoI recommend reading "Parallel and Concurrent Programming in Haskell" by Simon Marlow in which he explains that back in 1997 he decided to "build concurrency right into the core of the system rather than making it an optional extra or an add-on library".