
Michael Snoyman wrote:
I have a recommendation of how to fix this: the MonadCatchIO typeclass should be extended to include finally, onException and everything else. We can provide default definitions which will work for most monads, and short-circuiting monads like ErrorT (and I imagine ContT as well) will need to override them.
It seems that `finally' can be fixed without all these proposed additions. The method catch is the only necessary method of the class. The subject of catching errors in non-IO monads has a long history, some of which is documented at http://okmij.org/ftp/Haskell/index.html#catch-MonadIO The page points out to an old CaughtMonadIO file. I have just updated it for new Exceptions, and added the final test:
test3c go = runErrorT $ go `gfinally` (liftIO $ putStrLn "sequel called")
test31 = test3c (return "return" :: ErrorT String IO String)
*CaughtMonadIO> test31 sequel called Right "return"
test32 = test3c (error "error" :: ErrorT String IO String)
*CaughtMonadIO> test32 sequel called *** Exception: error
test33 = test3c (throwError "throwError" :: ErrorT String IO String)
*CaughtMonadIO> test33 sequel called *** Exception: ErrorException "\"throwError\"" As we can see, sequel is always called. Here is the updated file: http://okmij.org/ftp/Haskell/CaughtMonadIO.lhs Incidentally, one should be very careful of using `finally' with the continuation monad. The Cont monad lets us ``enter the room once, and exit many times''. So, finally may be called more than once. We need the ugly dynamic-wind -- or try to use less powerful monads if they suffice.

On Fri, Oct 15, 2010 at 9:35 AM,
Michael Snoyman wrote:
I have a recommendation of how to fix this: the MonadCatchIO typeclass should be extended to include finally, onException and everything else. We can provide default definitions which will work for most monads, and short-circuiting monads like ErrorT (and I imagine ContT as well) will need to override them.
It seems that `finally' can be fixed without all these proposed additions. The method catch is the only necessary method of the class.
The subject of catching errors in non-IO monads has a long history, some of which is documented at
http://okmij.org/ftp/Haskell/index.html#catch-MonadIO
The page points out to an old CaughtMonadIO file. I have just updated it for new Exceptions, and added the final test:
test3c go = runErrorT $ go `gfinally` (liftIO $ putStrLn "sequel called")
test31 = test3c (return "return" :: ErrorT String IO String)
*CaughtMonadIO> test31 sequel called Right "return"
test32 = test3c (error "error" :: ErrorT String IO String)
*CaughtMonadIO> test32 sequel called *** Exception: error
test33 = test3c (throwError "throwError" :: ErrorT String IO String)
*CaughtMonadIO> test33 sequel called *** Exception: ErrorException "\"throwError\""
As we can see, sequel is always called. Here is the updated file:
http://okmij.org/ftp/Haskell/CaughtMonadIO.lhs
Incidentally, one should be very careful of using `finally' with the continuation monad. The Cont monad lets us ``enter the room once, and exit many times''. So, finally may be called more than once. We need the ugly dynamic-wind -- or try to use less powerful monads if they suffice.
Perhaps I'm misunderstanding your code, but it seems like it's not really respecting the ErrorT monad at all. Instead, it's converting the error type to a runtime exception, which often times is not at all what we want. A pertinent example: in Yesod, I use a modified ErrorT to allow short-circuiting handler functions to perform special responses such as redirects. Using your code, I believe that such code on my part would result in a 500 server error every time, quite the opposite of what I wanted. I would prefer if the test read as:
test33 = fmap (== Left "throwError") $ test3c (throwError "throwError" :: ErrorT String IO String)
Which never in fact returns True. Or, more to the point, the test is never even called, since the runtime exception prevents it. Michael

Michael Snoyman wrote:
I would prefer if the test read as:
test33 = fmap (== Left "throwError") $ test3c (throwError "throwError" :: ErrorT String IO String)
Which never in fact returns True. Or, more to the point, the test is never even called, since the runtime exception prevents it.
If you prefer the raised exception to be reflected back into the monad it came from, that can be arranged. I have updated http://okmij.org/ftp/Haskell/CaughtMonadIO.lhs to use your test:
test331 = fmap (== Left (show "throwError")) $ test3c (throwError "throwError" :: ErrorT String IO String)
*CaughtMonadIO> test331 sequel called True The `show' is the artifact of `reconciling' Error and Exception classes. The class Error doesn't seem very informative; one may wonder if it is needed given that we already have Exception. If in your real code, the argument of throwError is actually an Exception, the show hack can be eliminated, where it is mentioned in the instance CaughtMonadIO (ErrorT e m). I can do the adjustment if you post more details about the desired functionality.

On Fri, Oct 15, 2010 at 10:22 AM,
Michael Snoyman wrote:
I would prefer if the test read as:
test33 = fmap (== Left "throwError") $ test3c (throwError "throwError" :: ErrorT String IO String)
Which never in fact returns True. Or, more to the point, the test is never even called, since the runtime exception prevents it.
If you prefer the raised exception to be reflected back into the monad it came from, that can be arranged. I have updated
http://okmij.org/ftp/Haskell/CaughtMonadIO.lhs
to use your test:
test331 = fmap (== Left (show "throwError")) $ test3c (throwError "throwError" :: ErrorT String IO String)
*CaughtMonadIO> test331 sequel called True
The `show' is the artifact of `reconciling' Error and Exception classes. The class Error doesn't seem very informative; one may wonder if it is needed given that we already have Exception. If in your real code, the argument of throwError is actually an Exception, the show hack can be eliminated, where it is mentioned in the instance CaughtMonadIO (ErrorT e m). I can do the adjustment if you post more details about the desired functionality.
By the way, I completely agree that the Error typeclass is not very useful, and I wish it would just disappear. I wrote the neither package[1]- the modified error monad I keep mentioning- in large part to avoid the Error typeclass. The other reason was the oprhan Monad Either instance. To the point at hand: I'm aware that you can promote the Error monad's error type into a runtime exception, catch it, and pull it back down. My point is that you shouldn't have to: there are perfectly valid definitions of finally for the error monad that don't require any of this trickery. Additionally, sometimes the error type cannot (reasonably) be promoted to an exception, eg ErrorT (a -> b) m. We'd have to start mucking around with dummy Show instances/blind wrappers. I just finished writing up a post describing the MonadInvertIO approach[2], which I think is a more appropriate solution to the problem. It doesn't involve any runtime exception trickery, and works for a number of other use cases, including memory allocation. I would appreciate any critiques of my ideas. Michael [1] http://hackage.haskell.org/cgi-bin/hackage-scripts/package/neither [2] http://docs.yesodweb.com/blog/invertible-monads-exceptions-allocations/#mona... (links straight to the appropriate section, skips the long buildup)
participants (2)
-
Michael Snoyman
-
oleg@okmij.org