| You're right that having some mechanism to act gracefully in the case of errors is almost always required. "Internally" this might be a "main loop" with an exception handler. "Externally" this might be a bash script which runs our binary in a loop, or a systemd service, etc. The intriguing thing about Haskell's approach is that it shows us that such mechanisms aren't pareto-improvements: we have to give up something, like confluence. Keep in mind that confluence isn't just academic, it's the thing which makes functional programming attractive for parallelism. Confluence solves the hard problem of taking all possible interleavings of concurrent execution into account, since they act like different evaluation orders, and hence can't mess up the result. Servers are a scenario where these two features clash: we want concurrency and parallelism for scaling, but we need restart loops to prevent downtime. The use of external restart loops reminds me of delimited continuations, where even "undelimited continuations" are still delimited by the OS (e.g. Scheme's current continuation doesn't include the state of other OS processes). Likewise, "unobservable errors" can still be observed by the OS (e.g. when our process dies, as in a bash or systemd loop). I think a good compromise is to "stratify" error handling: we write our business logic (or whatever) in a provably confluent sub-set of our language, and execute that logic using non-confluent features like error handlers. Confluent expressions can take advantage of optimisations (e.g. speculative evaluation) which are invalid for the wrapper. One thing I'm not sure about is nesting exception-handling code in pure code. Approaches like algebraic effect systems let us mark expressions as requiring effects like 'stdio', yet we can handle those effects in a pure way (e.g. using hard-coded strings during a test). I don't think this is enough to maintain confluence in the face of concurrency though; we'd probably have to pass in a deterministic scheduler, but that may parallelism gains of things like speculative evaluation, work-stealing, etc. |
This isn't (just) about exceptions that escape; you also need to guarantee that fst (0, ⊥) returns 0 rather than ⊥. Heck, you're practically required to do the same for
and for that evaluation order isn't even visible at the denotational level.> Keep in mind that confluence isn't just academic, it's the thing which makes functional programming attractive for parallelism.
Automatic parallelisation of functional languages is academic.