From: John Goerzen [mailto:jgoerzen@complete.org]
My other choice is to use Dynamic for my exceptions, but that makes it even more difficult to catch and handle
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
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?
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.
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?
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. Alistair. ----------------------------------------- ***************************************************************** Confidentiality Note: The information contained in this message, and any attachments, may contain confidential and/or privileged material. It is intended solely for the person(s) or entity to which it is addressed. Any review, retransmission, dissemination, or taking of any action in reliance upon this information by persons or entities other than the intended recipient(s) is prohibited. If you received this in error, please contact the sender and delete the material from any computer. *****************************************************************
Bayley, Alistair writes:
data SqliteException = SqliteException Int String deriving (Typeable)
catchSqlite :: IO a -> (SqliteException -> IO a) -> IO a catchSqlite = catchDyn
throwSqlite :: SqliteException -> a throwSqlite = throwDyn
I, too, think that's a good way to do it. And in the cases where a custom data type seems to be overhead, I simply use (fail "Foo.Bar: buffer overflow") and catch it with: isBufferOverflow :: IOError -> Bool isBufferOverflow e = (isUserError e) && (ioeGetErrorString e == "FooBar: buffer overflow") Once you have attributes for individual exceptions, defining them for arbitrary groups of exceptions is trivial. And the fact that I _use_ the message internally encourages me to use clear and consistent error messages which also make sense in case someone just 'show's them.
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?
We also have Control.Exception.try. :-) Peter
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
John Goerzen wrote:
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.
One of the things I like about exceptions in haskell is that there's relatively little magic, if a function like finally isn't there, you can implement it yourself. m `finally` f = do r <- m `catch` (\e -> f >> throw e) f return r (I'm ignoring asynchronous exceptions for now, see the code for Control.Exception for details) And I find bracket quite useful, it nicely captures a concept you have to code "by hand" in many other languages. And bracket is also a function you could have implemented yourself, if it hadn't been available. /Peter
participants (4)
-
Bayley, Alistair -
John Goerzen -
Peter Simons -
Peter Strand