This is actually pretty cool. This way the server stays in charge of the threads it spawns, rather than having to build in some auto-destruct feature into the thread itself (which may not necessarily fire). The thread becomes more like a sandboxed environment -- with resource quotas -- than an independent entity, which I think is what we want, if we want to be able to reason about our code.
Does anybody know how this is implemented in the runtime system? How does one thread communicate with another thread? And how do you need to look at this for it not to be a side-effect?
Any insight is much appreciated.
As for why it's not a side effect, in Haskell all exceptions are considered to be bottom, the value that inhabits every type. Thus an exception can be raised anywhere and have any type. They can only be handled in IO though.
I would look at it like this: while a function can be pure (no side effects), the thread in which it executes need not be a pure construct. It's a property of the runtime system, which, essentially, schedules pure functions to run inside green threads, to which exceptions can be thrown (which will terminate the execution of the pure function).
In other words, it's one of the things you don't care about when writing a pure program, like how many times it runs, how many threads are running it, on what order it executes, etc.
It does leak, but only on the IO monad.
>"...if [Haskell wants] to be able to interrupt ... asynchronous exceptions are the only way, because polling would be a side-effect"
File under "Indictments against Haskell". We're gonna need a bigger filing cabinet.
In Haskell, this turns out to be an occasionally challenging problem rather than an impossible one. As the article said, it does get tricky near a few of the seams, but in most code you can just ignore the problem and not worry about it. Your immutable computation gets interrupted at an arbitrary location in the middle of it, and you just throw away the pure components of the computation and run the cleanup code from "bracket"s. (And other runtime details.)
It's the same reason Haskell has usable STM and languages with unconstrained effects do not. (Yes, you can download libraries that are STM for a lot of languages, but they are generally not usable.) In a language with unconstrained mutation and side effects, you can't reasonably roll back a transaction because you can't undo the effects, and you can't constrain the programmer to not use any effects. As it turns out, STM turned out not to be a silver bullet and it's merely a single tool in Haskell's toolbelt, one that isn't even necessarily used that often, but because Haskell could implement it, the Haskell community was able to discover that by using it, rather than just theorizing about how nice it may or may not be if only we could have an implementation.