A generalized version of
bracket which uses
ExitCase to
distinguish the different exit cases, and returns the values of both
the
use and
release actions. In practice, this extra
information is rarely needed, so it is often more convenient to use
one of the simpler functions which are defined in terms of this one,
such as
bracket,
finally,
onError, and
bracketOnError.
This function exists because in order to thread their effects through
the execution of
bracket, monad transformers need values to be
threaded from
use to
release and from
release to the output value.
NOTE This method was added in version 0.9.0 of this library.
Previously, implementation of functions like
bracket and
finally in this module were based on the
mask and
uninterruptibleMask functions only, disallowing some classes of
tranformers from having
MonadMask instances (notably
multi-exit-point transformers like
ExceptT). If you are a
library author, you'll now need to provide an implementation for this
method. The
StateT implementation demonstrates most of the
subtleties:
generalBracket acquire release use = StateT $ \s0 -> do
((b, _s2), (c, s3)) <- generalBracket
(runStateT acquire s0)
(\(resource, s1) exitCase -> case exitCase of
ExitCaseSuccess (b, s2) -> runStateT (release resource (ExitCaseSuccess b)) s2
-- In the two other cases, the base monad overrides `use`'s state
-- changes and the state reverts to `s1`.
ExitCaseException e -> runStateT (release resource (ExitCaseException e)) s1
ExitCaseAbort -> runStateT (release resource ExitCaseAbort) s1
)
(\(resource, s1) -> runStateT (use resource) s1)
return ((b, c), s3)
The
StateT s m implementation of
generalBracket
delegates to the
m implementation of
generalBracket.
The
acquire,
use, and
release arguments
given to
StateT's implementation produce actions of type
StateT s m a,
StateT s m b, and
StateT s m
c. In order to run those actions in the base monad, we need to
call
runStateT, from which we obtain actions of type
m
(a, s),
m (b, s), and
m (c, s). Since each
action produces the next state, it is important to feed the state
produced by the previous action to the next action.
In the
ExitCaseSuccess case, the state starts at
s0,
flows through
acquire to become
s1, flows through
use to become
s2, and finally flows through
release to become
s3. In the other two cases,
release does not receive the value
s2, so its action
cannot see the state changes performed by
use. This is fine,
because in those two cases, an error was thrown in the base monad, so
as per the usual interaction between effects in a monad transformer
stack, those state changes get reverted. So we start from
s1
instead.
Finally, the
m implementation of
generalBracket
returns the pairs
(b, s) and
(c, s). For monad
transformers other than
StateT, this will be some other type
representing the effects and values performed and returned by the
use and
release actions. The effect part of the
use result, in this case
_s2, usually needs to be
discarded, since those effects have already been incorporated in the
release action.
The only effect which is intentionally not incorporated in the
release action is the effect of throwing an error. In that
case, the error must be re-thrown. One subtlety which is easy to miss
is that in the case in which
use and
release both
throw an error, the error from
release should take priority.
Here is an implementation for
ExceptT which demonstrates how
to do this.
generalBracket acquire release use = ExceptT $ do
(eb, ec) <- generalBracket
(runExceptT acquire)
(\eresource exitCase -> case eresource of
Left e -> return (Left e) -- nothing to release, `acquire` didn't succeed
Right resource -> case exitCase of
ExitCaseSuccess (Right b) -> runExceptT (release resource (ExitCaseSuccess b))
ExitCaseException e -> runExceptT (release resource (ExitCaseException e))
_ -> runExceptT (release resource ExitCaseAbort))
(either (return . Left) (runExceptT . use))
return $ do
-- The order in which we perform those two `Either` effects determines
-- which error will win if they are both `Left`s. We want the error from
-- `release` to win.
c <- ec
b <- eb
return (b, c)