Conditions and restarts aren’t much better or worse than standard exceptions in that[1] respect.
You can basically implement them[2] on top of those + dynamic scope: the gist is that when an exceptional condition happens, instead of destructively unwinding the stack immediately the code only invokes a callback (selected by condition type, installed by some dynamically enclosing code), which then can choose one of the available handling strategies and unwind the stack to the requisite point (using try / throw if implementing on top of that). The fun part is that it can unwind less than to the point where the callback was installed: e.g. a REPL installs a handler that, on an undefined variable error, asks the user interactively for the value to use instead, invokes the language-provided USE-VALUE restart and proceeds; no debugger-level carnal knowledge of the language runtime is necessary at any point. (Compare also the DOS abort/retry/ignore prompt. Win32 SEH is also capable of expressing a certain degree of resumable exceptions not exposed by C++ runtime, as seen in classic VB’s On Error Resume Next—though that is admittedly a bad idea.)
The advantage[3] of this construct is that it enables you to structure APIs differently, in that the knowledge of how to recover need not exist at the same level as the decision to recover that way. (The potential recovery strategies become part of the API the same way that potential errors are.) That eliminates the “handle-those-errors-this-way” flag awkwardness that exception handling can’t really mitigate. The API-design expressive power, so to say, is different even if the control-flow expressive power[4] is basically the same.
(A minor point is that because there’s no mandatory unwinding, the signalling code can also decide to proceed if the condition is unhandled. This subsumes warning systems such as the one in Python[5]. The point is minor because I haven’t seen those play an important role.)
> I don't know much about algebraic effects, but usually "algebraic" implies various guarantees about composability and idempotence, which are exactly what you want when dealing with state and the world.
The guarantees of composability are the point, yes—compared to monad transformers. In that respect the article, to me, seems to miss some of the point of the algebraic effects research: the languages there usually try to isolate all non-referentially-transparent execution in the effect system, not to introduce it as an additional system. This includes ordinary mutability (of things that outlive a function call, you’re free to mutate temporaries as long as you destroy them at the end). The ultimate in function colouring, so to say. Monads were a previous attempt at the same, and monad transformers do add some composability to them, but using the result kind of sucks. We’ll see how well effects do.
Algebraic effects are also more powerful than resumable exceptions, because exception resumption is usually one-shot—either you resume or you don’t—whereas both Eff and Koka allow you to define a nondeterminism effect that allows you to resume more than once (e.g. for backtracking).
I’d still say the article’s point is interesting, because conditions systems originate from the Lisp tradition and so there doesn’t seem to be a lot of literature on typing them statically. It’s nice to realize that research has already been done under the banner of typing algebraic effects.
[1] https://vorpus.org/blog/notes-on-structured-concurrency-or-g...
[2] https://news.ycombinator.com/item?id=31196046
[3] https://gigamonkeys.com/book/beyond-exception-handling-condi...