Lost Computation

By Artyom Bologov Almost monochrome thumbnail. In the center, huge letters spell out “LOST compute”. “LOST” is colorfully written as if slowly disappearing, with each letter becoming more dashed and spacious. In the corners, small and equally dashed attributions to “aartaka.me” and “Artyom Bologov” are barely visible. The artist sure aimed for aesthetics over readability.

It immensely saddens me when the computation and context is lost. Because it’s there for a reason. More context means better error messages and debugging state preservation. And these result in better developer and user experience. Yet we programmers constantly lose running computation and its context. What a waste.

Case Study: a Web Server #

So imagine a simple Web server. A set of request handlers, thread pool, some dispatcher running handlers on requests. Every handler does work on the request, like querying/modifying DB, crunching numbers, or whatever. Handlers also rely on session data (cookies, API tokens, browser fingerprints) to make decisions. Clear so far, right?

Now what if something goes wrong at the DB level? An exception/error/condition is thrown. Depending on who’s catching it, the consequences differ:

Request handler
Has all the context and can return reasonable error message/status. But every request handler needs its own catch clause, and usually more than one.
Handler dispatcher
Is more generic than request handler, so lacks some context, like handler stack and interim values. Can only return generic error message/status.
Thread-specific catches
Even more generic, lack the whole context of Web Server except for some global stats. 404 and 500.
A whole application
No idea what’s going on, I guess I just collapse.

You get the trade-offs: catching errors at the leaves of the app is tedious and repetitive. While catching up the branches is too generic and loses context and running computation.

Solution: Break Loops and Dynamic Environment Handlers #

It’s a shame most programming languages adopted the exception-driven stack-unwinding model of errors. Because these are to blame for lost state. Luckily, some programming systems have more reliable error handling that doesn’t lose state/compute.

Common Lisp, for example. There’s this handler-bind block that catches an error and acts in its dynamic environment. Meaning: all the state of e.g. erroring request is there for the handler to act on. With the ability to restart the whole computation from where it stopped once you fix the state. Sometimes even interactively, if the user wants to.

I know this type of error environment inspection is a luxury that most languages don’t provide. But it’s also a proof for what’s possible, so why settle for less?

Another Solution: Context Passing #

But what if we didn’t throw at all? If we return error values, then we don’t lose context we can store in them. That’s what Go (second return value,) Haskell (Left/Right,) and Clojure (context maps) settled on.

Dave Liepmann wrote on Clojure’s approaches to errors. So I’ll just refer you to his post for a better overview.

The problem is that one has to explicitly pass the state along with an error. And no one wants to do that. So this approach is worse than the automatic environment inspection that Lisp allows. But still better than destroying all the state.


Whatever way to preserve the erroring state you pick, I beg of you. Please, don’t lose computation.

Leave feedback! (via email) #