Takusen - error handling and DBM monad

I'm continuing (in my occasional spare time) to try to write a "simple database query" in takusen. The query is OK now, but I'm struggling with error handling. I have a simple error handler catcher :: DBException -> DBM mark Session () catcher x = do liftIO $ putStrLn $ show x All it does is print the exception. The DBM mark Session () type, I deduced by fiddling until something works. Now, I can protect my query with something like main = do withSession (connect "..." "..." "...") ( do catchDB (do -- simple query, returning reversed list of rows. r <- doQuery (sql "select username from all_users") query1Iteratee [] liftIO $ putStrLn $ show r ) catcher ) So far, so good. The syntax is messy, but a little refactoring should fix that. But this doesn't catch errors in the connect call. Wrapping the withSession in catchDB doesn't work, as withSession is in the IO monad [1], not the DBM one. But using catch with catcher doesn't work, as that expects an error handler in the IO monad, but catcher is in the DBM monad. I'm getting very confused about the relationship between the DBM stuff and the IO monad. I'm sure there are good reasons for the DBM monad, but at the moment I'm missing them, and all I'm doing is trying random things, to little avail. If someone could give me a better understanding of the reasoning behind the DBM stuff, plus some pointers on how I should be interleaving IO and DBM stuff, I'd be hugely grateful. Apologies if this post is vague and/or badly expressed, but I'm currently a bit short of time, and fairly frustrated over what I feel should be a simple task (that imperative vs functional mismatch, again :-)) Thanks, Paul. [1] Excuse sloppy terminology here!

Paul Moore wrote:
Now, I can protect my query with something like
main = do withSession (connect "..." "..." "...") ( do catchDB (do -- simple query, returning reversed list of rows. r <- doQuery (sql "select username from all_users") query1Iteratee [] liftIO $ putStrLn $ show r ) catcher )
So far, so good. The syntax is messy, but a little refactoring should fix that.
But this doesn't catch errors in the connect call.
Wrapping the withSession in catchDB doesn't work, as withSession is in the IO monad [1], not the DBM one. But using catch with catcher doesn't work, as that expects an error handler in the IO monad, but catcher is in the DBM monad.
You can catch exceptions in the IO monad using 'catch' from the prelude: http://haskell.org/ghc/docs/latest/html/libraries/base/Prelude.html#v%3Acatc... Unfortunately, you will have to write another error handler though, because the catch expects the handler to have type (IOError -> IO a) instead of (DBException -> DBM ...). Using both catch and catchDB should let you handle all the errors. BTW, what does withSession return if there is a DBException?

From: haskell-cafe-bounces@haskell.org [mailto:haskell-cafe-bounces@haskell.org] On Behalf Of Paul Moore
catcher :: DBException -> DBM mark Session () catcher x = do liftIO $ putStrLn $ show x
main = do withSession (connect "..." "..." "...") ( do catchDB (do ... ) catcher )
But this doesn't catch errors in the connect call.
Wrapping the withSession in catchDB doesn't work, as withSession is in the IO monad [1], not the DBM one. But using catch with catcher doesn't work, as that expects an error handler in the IO monad, but catcher is in the DBM monad.
There's an example in the README file, in which we see: main = flip catchDB reportRethrow $ withSession (connect "sqlite_db") (do ... which basically wraps withSession with catchDB. This does catch errors in the connect call. The difference between your example and this is in the handler; your handler has type:
catcher :: DBException -> DBM mark Session ()
whereas the README example handler is from the library, and has type:
reportRethrow :: CaughtMonadIO m => DBException -> m ()
CaughtMonadIO isn't something we've bothered to explain in the docs, but this is simply a way of catching exceptions in a monad that is in the MonadIO class, which is something the standard libraries don't support. In Control.Exception, catch and friends are stuck in the IO monad. This problem has been discussed before on this list: http://www.haskell.org/pipermail/haskell/2006-February/017547.html http://www.haskell.org/pipermail/haskell/2006-April/017893.html And may well be fixed for Haskell-Prime? http://hackage.haskell.org/cgi-bin/haskell-prime/trac.cgi/ticket/110 So you just need to float your handler out one level (so it wraps withSession), and make it usable in the regular IO monad :-) That should be as simple as changing the type sig:
catcher :: CaughtMonadIO m => DBException -> m () catcher x = liftIO $ putStrLn $ show x
There are a couple of simple handlers in Database.Enumerator already, which I'd recommend you start with:
basicDBExceptionReporter :: CaughtMonadIO m => DBException -> m () basicDBExceptionReporter e = liftIO (putStrLn (formatDBException e))
reportRethrow :: CaughtMonadIO m => DBException -> m () reportRethrow e = basicDBExceptionReporter e >> IE.throwDB e
If you need something fancier then I suggest copying the code in Database.Enumerator and modifying to suit your needs.
I'm getting very confused about the relationship between the DBM stuff and the IO monad. I'm sure there are good reasons for the DBM monad, but at the moment I'm missing them, and all I'm doing is trying random things, to little avail.
The idea is to prevent resources used in database code from escaping into other, non-database, parts of your program. This lets us safely use the with- idiom to manage resources. For example, although code in the DBM monad has access to the connection object (somewhat indirectly, but it is there), it cannot return this object out of the DBM monad. When withSession closes the connection, we can be sure that further access is not possible. This is a similar technique to that used by the ST monad, I think. Oleg explains it here: http://www.haskell.org/pipermail/haskell/2006-January/017410.html Please feel free to mail me directly with questions, too, at this address or alistair@abayley.org (although my access to that maibox during working hours is sporadic and infrequent). Al Falloon wrote:
what does withSession return if there is a DBException?
Well, whatever the handler returns, same as with any other exception handler. Note that this must have the same type as whatever withSession returns, and this constraint is enforced by the type of catch/catchDB:
catchDB :: CaughtMonadIO m => m a -> (DBException -> m a) -> m a
... which is modelled on Control.Exception.catch (for regular IO monad exceptions):
catch :: IO a -> (Exception -> IO a) -> IO a
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 wrote:
Al Falloon wrote:
what does withSession return if there is a DBException?
Well, whatever the handler returns, same as with any other exception handler. Note that this must have the same type as whatever withSession returns, and this constraint is enforced by the type of catch/catchDB:
Sorry, I wasn't clear. What does it return when there is an uncaught exception? It sounds like it raises an exception in IO. Is this correct?

From: haskell-cafe-bounces@haskell.org [mailto:haskell-cafe-bounces@haskell.org] On Behalf Of Al Falloon
Bayley, Alistair wrote:
Al Falloon wrote:
what does withSession return if there is a DBException?
Well, whatever the handler returns, same as with any other exception handler. Note that this must have the same type as whatever withSession returns, and this constraint is enforced by the type of catch/catchDB:
Sorry, I wasn't clear. What does it return when there is an uncaught exception? It sounds like it raises an exception in IO. Is this correct?
Well, a function raising an uncaught exception doesn't really return anything - it's raised an exception. I'm not clear on the semantics of exceptions in Haskell, but if you don't catch it then eventually it reaches the RTS, which will halt your program with an uncaught exception error. If you're asking if it propagates up to the RTS, then the answer is yes. The downside is that because it's a dynamic exception, the RTS can't/won't show anything other than "uncaught dynamic exception", which isn't helpful. 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. *****************************************************************
participants (3)
-
Al Falloon
-
Bayley, Alistair
-
Paul Moore