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.
-
Haskell programmers tend to think about the behaviour of
f `catch` ga little differently. They don't think aboutgas an exception handler. Instead, bothfandgare just two possible ways to produce a result. First we try to produce a result usingf. If that fails, we try to produce it usingg. TheAlternativetype class, of whichMaybeis an instance, provides the operator(<|>)that provides exactly this logic. Sof `catch` gis the same asf <|> g. The latter naturally extends to multi-way alternatives: Tryf, theng, thenh, thenibecomesf <|> g <|> h <|> i.((f `catch` g) `catch` h) `catch` i, readingcatchas exception handling, seems much less natural in this context. ↩