[Haskell] Better Exception Handling

John Goerzen jgoerzen at complete.org
Tue Nov 23 10:07:42 EST 2004


Hi everyone,

I've been using Haskell for 1-2 months now, and feel fairly comfortable
with the language.  However, my #1 gripe is the difficulty of working
with exceptions.  I have two main complaints: difficulty of defining
custom exceptions, and difficulty of handling exceptions.

I've been working on a library of various bits of Haskell code.  There
are several things that could raise exceptions within that library.
There's no real easy way to do it, and make it easy for people using the
library to trap only my exceptions.  I could just use error("foo: bar")
and tell them to look for a string that starts with "foo:".  It works,
but it's clumsy, and unreliable too ("foo:" is a valid filename, for
instance, and could be used in other exceptions.)  My other choice is to
use Dynamic for my exceptions, but that makes it even more difficulty to
catch and handle, and requires the programmer to be ready to deal with a
fairly esoteric part of Haskell.

In OCaml, there is an exception keyword, that is essentially the same as
a data keyword.  It defines a new exception, and can use the same
constructors, etc. that any other type can.  It's very useful.

In Python, the situation is even better.  There, an object can be
thrown as an exception.  Moreover, when catching exceptions, you can
match an exception by a particular object *or any of its parents*.  That
means that I can easily extend someone else's work, adding my own
exceptions as child objects of the existing ones.  When I want to catch
exceptions, I can be as specific or as general as I want, and any code
designed to catch the more general exceptions will keep working.  Java
also works this way.

Now, on to catching errors.  OCaml makes this easy:

try (whatever) with
  End_of_file -> foo
| My_custom_error x -> bar x

Here, "whatever", "foo", and "bar x" must all be of the same type.  If there
is no error, whatever is returned.  If there is an error that we catch,
the given value is returned.  Otherwise, the exception is passed on up.

Python can work that way, but also adds another feature:

try:
    blah
    moreblah
finally:
    foo

The code "foo" will be executed whenever an exception occurs in the try
block, or whenever the block ends normally.  In other words, it is
*always* executed.  This is useful for doing cleanup actions like
closing network connections.

Haskell's exception catching doesn't really have less functionality than
OCaml (some might argue it has more), but it takes a lot more effort to
compose, needing to provide a function that returns a function in many
cases.

The other annoying thing is forcing it to run in the IO monad.  I don't
really understand this restriction.  If we're not dealing with IO code
to start with, we have deterministic behavior (if something raises an
exception on a given value once, it will do that every time).

So why do we have:

catchJust :: (Exception -> Maybe b) -> IO a -> (b -> IO a) -> IO a

instead of:

catchJust :: (Exception -> Maybe b) -> (c -> a) -> c -> (b -> a) -> a

This would let us run an arbitrary function (so long as it takes one
parameter) and handle the exception completely outside of the IO monad.
I can't think of a theoretical reason that this wouldn't work, though I
suppose I may be missing something.

Thoughts?  Flames? :-)

-- John




More information about the Haskell mailing list