The documentation complains about how monad-peel/monad-control’s implementation of bracket discards the monadic effects of the finalizer computation. I’d like to explain why this was intentional, by way of example:
import Control.Monad.Trans
import Control.Monad.Trans.List
-- import Control.Exception.Peel -- monad-peel
-- import Control.Exception.Lifted -- monad-control
import Control.Monad.Interface.Try -- layers
dup :: Int -> ListT IO ()
dup n = ListT (return (take n (repeat ())))
m :: IO [()]
m = runListT $ bracket
(lift (putStrLn "init"))
(\() -> lift (putStrLn "close"))
(\() -> dup 3 >> lift (putStrLn "body"))
monad-peel/monad-control gives the expected result:
λ> m
init
body
body
body
close
[(),(),()]
while layers duplicates the finalizer three times:
λ> m
init
body
body
body
close
close
close
[(),(),()]
In general, when replacing dup 3 by dup n, monad-peel/monad-control always runs the finalizer exactly once, while layers runs it max {n, 1} times. The latter behavior would be bad for finalizers that expect to be paired one-to-one with initializers (e.g. malloc/free).
The zero logic in MonadLayerControl looks like it was intended as a hack to deal with short-circuiting monads, but it comes at the expense of mishandling more general kinds of side effects. If you think your short-circuiting behavior is important, zero should probably be moved into a more specific typeclass that ListT does not satisfy, or perhaps the MonadLayerControl instance for ListT should just be dropped, and the documentation should clarify that a False return from zero indicates that the LayerState m a contains exactly one a in it, not more. It may be cleaner to enforce this invariant at the type level by replacing LayerState m a with Either (BadState m) (a, GoodState m) (and zero with either True False), since that seems like the only kind of LayerState that will give the intended semantics.
The documentation complains about how monad-peel/monad-control’s implementation of
bracketdiscards the monadic effects of the finalizer computation. I’d like to explain why this was intentional, by way of example:monad-peel/monad-control gives the expected result:
while layers duplicates the finalizer three times:
In general, when replacing
dup 3bydup n, monad-peel/monad-control always runs the finalizer exactly once, while layers runs it max {n, 1} times. The latter behavior would be bad for finalizers that expect to be paired one-to-one with initializers (e.g.malloc/free).The
zerologic inMonadLayerControllooks like it was intended as a hack to deal with short-circuiting monads, but it comes at the expense of mishandling more general kinds of side effects. If you think your short-circuiting behavior is important,zeroshould probably be moved into a more specific typeclass thatListTdoes not satisfy, or perhaps theMonadLayerControlinstance forListTshould just be dropped, and the documentation should clarify that aFalsereturn fromzeroindicates that theLayerState m acontains exactly oneain it, not more. It may be cleaner to enforce this invariant at the type level by replacingLayerState m awithEither (BadState m) (a, GoodState m)(andzerowitheither True False), since that seems like the only kind ofLayerStatethat will give the intended semantics.