
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. Carl Howells

On 28/06/2010 20:02, Carl Howells wrote:
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:
See also this recent thread on haskell-cafe: http://www.haskell.org/pipermail/haskell-cafe/2010-June/079198.html which concluded that the ContT instance in MonadCatchIO is broken. I think as you say, the instances of that library are flawed when dealing with monads that can alter the control flow. Thanks, Neil.

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

There’s a history of rich debate and discussion on these issues coming from the scheme world, where it takes the guise of implementing unwind-protect in the presence of call/cc. Kent Pitman’s take is presented here: http://www.nhplace.com/kent/PFAQ/unwind-protect-vs-continuations-overview.ht... There’s some context given in the following ltu discussion: http://lambda-the-ultimate.org/node/2966 Will Clinger’s notes on his revised implementation are particularly useful: http://www.ccs.neu.edu/home/will/UWESC/uwesc.sch Note that none of the implementations translate directly, I think, as they rely on top-level mutable state. In any case, my take is the following -- first, remove the ContT instance from the CatchIO package as it is obviously wrong. Next, don’t use ContT in the presence of exceptions. There are few cases where one really needs to do so, given that exceptions provide a powerful means of flow control themselves. Finally, it is much easier to provide a bracket function with one-shot continuations (i.e., MonadExit), and doing so could well simplify most of the current uses of ContT. Cheers, Sterl
participants (4)
-
Carl Howells
-
Michael Snoyman
-
Neil Brown
-
Sterling Clover