Skip to content

bracket with ListT duplicates finalizer calls #4

Description

@andersk

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions