
On Mon, Jun 28, 2010 at 10:02 PM, Carl Howells
While working this weekend on the Snap web framework, I ran into a problem. Snap implements MonadCatchIO, so I thought I could just use bracket to handle resource acquisition/release in a safe manner. Imagine my surprise when bracket simply failed to run the release action sometimes.
I quickly determined the times when it doesn't run are when Snap's monadic short-circuiting is used. I dug into the source of bracket (in the transformers branch, though the mtl branch has the same behavior in these cases, with slightly different code), and the reason why quickly became obvious:
-- | Generalized version of 'E.bracket' bracket :: MonadCatchIO m => m a -> (a -> m b) -> (a -> m c) -> m c bracket before after thing = block $ do a <- before r <- unblock (thing a) `onException` after a _ <- after a return r
When monadic short-circuiting applies, the "_ <- after a" line gets completely ignored. In discussions with #haskell on this topic, it quickly became clear that for any monad transformer that can affect control flow, the definition of bracket in MonadCatchIO doesn't keep the guarantee provided by bracket in Control.Exception, which is that the "after" action will be run exactly once.
Because of that, I think bracket needs to be a class function. Furthermore, I think it needs to be a new class, ie
class MonadCatchIO m => MonadBracketIO m where bracket :: m a -> (a -> m b) -> (a -> m c) -> m c
This would allow its definition in cases where it makes sense (Snap or MaybeT IO), but it could be left out in cases where it doesn't make sense, like ListT IO, even though MonadCatchIO makes sense there.
I'm not sure if it's related to this, but I had a problem[1] using the ContT instance of MonadCatchIO, but there the result was the opposite: I would have resources released twice. I would really like to have that one solved as well.
Michael [1] http://permalink.gmane.org/gmane.comp.lang.haskell.cafe/76262