[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
bit.
> 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")
try:
try:
f.cwd('/pub/linux/kernel/v2.4')
except (ftplib.error_perm e):
print "I can't access the directory", e
sys.exit(2)
f.retrbinary("RETR ChangeLog-2.4.13",
lamba block: sys.stdout.write(block))
f.quit()
except (ftplib.error_perm e):
print "Permissions error ", e
sys.exit(2)
except (ftplib.error_temp e):
print "Temporary error, please try again later", e
sys.exit(1)
except (ftplib.all_errors e):
print "Other FTP error", e
sys.exit(2)
except e:
print "Non-FTP error", e
sys.exit(3)
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
throwDyn.
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
non-intuitive.)
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