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