[Haskell] Better Exception Handling

John Goerzen jgoerzen at complete.org
Tue Nov 23 11:20:15 EST 2004

On Tue, Nov 23, 2004 at 03:43:09PM -0000, Bayley, Alistair wrote:

Thanks for your thoughtful reply.  Let me try to expand on it a little

> Here's how I create "custom" exceptions; it doesn't seem onerous to me, but
> then I have a high tolerance for some kinds of coding pain:
> > data SqliteException = SqliteException Int String
> >   deriving (Typeable)
> > catchSqlite :: IO a -> (SqliteException -> IO a) -> IO a
> > catchSqlite = catchDyn
> > throwSqlite :: SqliteException -> a
> > throwSqlite = throwDyn

That works well enough if your code is limited to Sqlite exceptions.
But if you are at a higher level in the code -- you might be handling
Sqlite exceptions, or maybe IO exceptions, or maybe some other
exceptions -- it gets more difficult.  You have catchSqlite to use, then
there are the System.IO.Error catch/try functions, and then of course
there are the Control.Exception catch/try functions, which have the same
name but don't do the same thing.  If you are using any other toolkits
too, you may have more of a problem yet.

> > Python can work that way, but also adds another feature:
> > 
> > try:
> >     blah
> >     moreblah
> > finally:
> >     foo
> And in Haskell we have catch(Dyn), bracket, and finally. Are these not
> enough?

I hadn't been aware of finally.  That does seem to help.

> Does it? I'm not convinced... I think it's no more verbose than any other
> exception-handling mechanism, but maybe there's some cognitive overhead in
> translating an OO exception-handling idiom into Haskell. All I've ever used
> is catch and bracket, and I find them fairly straightforward. Could you post
> some code which you think would be clearer with an Ocaml or Python exception
> handling style?

Sure.  Here's a Python example... this isn't necessarily completely
accurate code, but it should be close:

import ftplib, os, sys

f = ftplib.FTP("ftp.kernel.org")
    except (ftplib.error_perm e):
        print "I can't access the directory", e
    f.retrbinary("RETR ChangeLog-2.4.13", 
                lamba block: sys.stdout.write(block))
except (ftplib.error_perm e):
    print "Permissions error ", e
except (ftplib.error_temp e):
    print "Temporary error, please try again later", e
except (ftplib.all_errors e):
    print "Other FTP error", e
except e:
    print "Non-FTP error", e

The various ftplib errors, when printed, will show the error code and
message from the server.  In a more complex program, these can
definately make a difference; for instance, if you get a permissions
error when trying to get a directory listing, you may treat it
differently than a permanent error (some directories exist but cannot be
listed, so it's a different situation.)

So, in Haskell, we would have to define a way to communicate what class
of error we have from FTP.  Perhaps a data type that could be used with

Next, for our outer loop, we'd have to do something like:

exctest :: FTPExc -> IO a
exctest e = case e of
   ErrorPerm x -> foo x
   ErrorTemp x -> bar x
   _ -> "other ftp error"

Then, we'd have to be able to deal with the non-FTP exceptions.  So, if
I'm getting this right, the handler code would look something like this,
where dlFile is the file downloading function:

catch (catchDyn dlFile exctest) (\a -> "non-ftp error" ++ show a)

It works, but it's not all that great, and it would be particularly
nasty if I tried to write it out inline, although that makes the code
most readable.  It would be even worse if I needed to handle several
different types of Dynamic exceptions, since I'd have to have a separate
handler function and a separate catchDyn for each.  (Or, I'd have to
just accept the Dynamic from catch, and handle it manually with
fromDynamic, which is not that much more pleasant and certainly

Part of what I'm getting at here is ease of using exceptions and
maintaining code.  I've seen a lot of Haskell code, from a lot of good
Haskell progammers, that just uses fail/error with a certain pattern in
a string because it's easier than going through the Dynamic mechanism.

> > The other annoying thing is forcing it to run in the IO monad. 
> Note that you can throw exceptions from anywhere. They just have to be
> *caught* in the IO monad.

Right, I got that.  My point was that if a function has deterministic
failure, as in my (String -> Int) example, I'm not certain why it has to
be that way.

-- John

More information about the Haskell mailing list