Skip to content

Catching Exceptions

So we have the first part of exception handling: If an exception arises—here, if a function fails and returns Nothing—then the entire computation fails, and thus also produces Nothing. Now, how do we catch exceptions? Let's look at the semantics of exception handling in Java once more. In

try {
    ...
} catch (Exception e) {
    ...
}

the result of the computation is whatever the try block produces if there is no exception. Otherwise, the try block aborts and the catch block tries to recover from the abnormal situation and produces the result of the computation. Note that the catch block may fail itself and throw an exception. It can even give up and "rethrow" the exception it was given in the first place.

In Haskell, we'd like to be able to write f `catch` g to mean that f is a computation that may fail: it has type Maybe a. If f succeeds, then g is never run and the result is whatever f produces. If f fails, then g is the exception handler. Since it should be able to recover and produce the result that f failed to produce, its return type should be a, or rather Maybe a, because g's efforts to recover from the exception may fail, in which case the whole computation f `catch` g fails. Thus, f `catch` g also has type Maybe a. This gives the following implementation of catch:1

catch :: Maybe a -> Maybe a -> Maybe a
f `catch` g = maybe g Just f

This is still a pretty poor implementation of exception handling. If f fails, g has no information about what went wrong. That's in the nature of using Maybe to implement failure and exception handling. By definition, failure is represented by Nothing, so it carries no information other than that the computation failed. We'll rectify this after introducing do-notation as a much more convenient way to write monadic computations. "Monadic computations" is the term I tend to use to mean computations composed of functions that decorate their return values using some monad, such as Maybe.


  1. Haskell programmers tend to think about the behaviour of f `catch` g a little differently. They don't think about g as an exception handler. Instead, both f and g are just two possible ways to produce a result. First we try to produce a result using f. If that fails, we try to produce it using g. The Alternative type class, of which Maybe is an instance, provides the operator (<|>) that provides exactly this logic. So f `catch` g is the same as f <|> g. The latter naturally extends to multi-way alternatives: Try f, then g, then h, then i becomes f <|> g <|> h <|> i. ((f `catch` g) `catch` h) `catch` i, reading catch as exception handling, seems much less natural in this context.