[Haskell-cafe] Re: MonadCatchIO, finally and the error monad

Michael Snoyman michael at snoyman.com
Fri Oct 15 03:55:57 EDT 2010


On Fri, Oct 15, 2010 at 9:35 AM,  <oleg at okmij.org> wrote:
>
> 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


More information about the Haskell-Cafe mailing list