By the way, here is how I would implement the ErrorT MonadCatchIO instance:
instance (MonadCatchIO m, Error e) => MonadCatchIO (ErrorT e m) where m `catch` f = mapErrorT (\m' -> m' `catch` \e -> runErrorT $ f e) m block = mapErrorT block unblock = mapErrorT unblock bracket before after thing = block $ do a <- before unblock $ thing a `finally` after a bracket_ before after thing = block $ do _ <- before unblock $ thing `finally` after finally thing after = mapErrorT (`finally` runErrorT after) thing By using "finally" inside of the definitions of bracket, bracket_ and finally, we can ensure that if there is any "special" monad underneath our ErrorT, the cleanup function will still run. Michael On Thu, Oct 14, 2010 at 12:01 PM, Michael Snoyman <mich...@snoyman.com> wrote: > Hey all, > > In case anyone noticed, Haskellers occassionally dies with a "Pool > exhausted exception." I've traced this to a bug in Yesod, which in > turn is a bug in the neither package, which I believe is a flawed > design in the MonadCatchIO-transformers package. Here are my thoughts > on this and what I think needs to be done to fix it. > > In Control.Exception, we define a number of different ways of dealing > with exceptions. All of these can be expressed in terms of block, > unblock and catch. For our purposes here, I'm going to ignore block > and unblock: they deal with asynchronous exceptions, which is not my > point here. Keep that in mind with the code samples. Anyway, with this > caveat, we can define finally as: > > finally :: IO a -> IO b -> IO a > a `finally` sequel = do > r <- a `catch` \e -> sequel >> throwIO (e :: SomeException) > _ <- sequel > return r > > The idea is simple: try to perform the action. If any exceptions get > thrown, call sequel and rethrow the exception. If we ever get to line > 4, it's because no exceptions were thrown. Therefore, we know that > sequel has not yet been called, so we call it. Said another way: there > are precisely two cases: > > * An exception was thrown > * An exception was not thrown > > A downside of this finally function (and catch, for that matter) is > that it requires all of the actions to live in the IO monad, when in > fact we all love to let things run in complicated monad transformer > stacks. So along comes MonadCatchIO-(transformers, mtl) and gives us a > new magical definition of catch: > > catch :: (MonadCatchIO m, Exception e) => m a -> (e -> m a) -> m a > > Using this new, extended definition of catch, we can define a finally > function with the type signature > > > finally :: MonadCatchIO m => m a -> m b -> m a > > (Note that we need to replace throwIO with liftIO . throwIO.) You can > try this with writers, readers, etc, and everything works just fine. > You can even use an Error/Either monad transformer, throw an > exception, and the finally function will correctly run your sequel > function. > > However, things don't work out so well when you use a throwError. > Let's see the code: > > {-# LANGUAGE PackageImports #-} > import Control.Monad.Trans.Error > import "MonadCatchIO-transformers" Control.Monad.CatchIO (finally) > import Control.Monad.IO.Class > > main = runErrorT $ finally go $ liftIO $ putStrLn "sequel called" > > go :: ErrorT String IO String > --go = return "return" > --go = error "error" > --go = throwError "throwError" > > Try running the code with each version of go uncommented. In the first > two, "sequel called" gets printed. However, in the third, it does not. > The reason is short-circuiting: if we remember from the definition of > finally, there are two cases we account for. If an exception is > called, catch addresses it. If not, we assume that the next line will > be called. However, in the presence of short-circuiting monads like > ErrorT, that line of code will never get called! > > 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. > > Michael > _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe