vintage-basic-1.0.3: src/Control/Monad/CPST/DurableTraps.hs
{-# LANGUAGE FlexibleContexts, RankNTypes #-}
-- | Support for advanced exception handling within the CPST monad.
module Control.Monad.CPST.DurableTraps where
import Control.Monad.CPST
import Control.Monad.CPST.ExceptionHandlers(capture,install,raise)
class ResultType o where
okValue :: o
-- | A continuation in the CPST monad, for code that may
-- throw exceptions.
type Cont o m i = i -> CPST (Excep o m i) m (Excep o m i)
-- | The full-blown exception including not only an exception value,
-- but a handler stack and continuation. Having all this allows
-- an exception handler to manipulate the handler stack or resume
-- using the exception's continuation.
--
-- [@o@] is the result type (e.g., OK, or an exception value)
--
-- [@i@] is the type to pass to the continuation (bound to the raise)
--
-- Both types must remain the same throughout your CPS computation.
data Excep o m i =
Excep { exceptionValue :: o
, exceptionHandlerStack :: (Excep o m i -> CPST (Excep o m i) m (Excep o m i))
, exceptionContinuation :: (Cont o m i)
}
-- | Packages the current continuation with the exception value,
-- and an empty handler stack.
raiseCC :: (Monad m, ResultType o) => o -> CPST (Excep o m i) m i
raiseCC x = callCC (\k -> raise (Excep x return k))
type ExceptionResumer o m = forall a. Bool -> CPST (Excep o m ()) m a
type ExceptionHandler o m = Monad m => o -> ExceptionResumer o m -> ExceptionResumer o m
-> ExceptionResumer o m -> CPST (Excep o m ()) m (Excep o m ())
-- | A flexible exception handling mechanism. It takes a function 'f' as a
-- parameter, to which it passes three continuations: @passOn@, @resume@,
-- and @continue@.
--
-- [@passOn@] pass control of the exception on up the handler chain (re-raise)
--
-- [@resume@] return to where the exception was raised (the exception's continuation)
--
-- [@continue@] continue with the code following the 'trap' statement
-- (the handler's continuation)
--
-- Each of these continuations is parametrized by a boolean value:
--
-- [@endure = True@] keep this handler in force
--
-- [@endure = False@] disable this handler (remove it from the chain)
trap :: Monad m
=> ExceptionHandler o m -- ^ exception handler
-> CPST (Excep o m ()) m ()
-- Local variables:
--
-- [@h hk@] the full handler with all logic
--
-- [@xv @] exception value
--
-- [@hk @] handler's continuation
--
-- [@hc @] handler chain
--
-- [@xk @] exception's continuation
trap f = callCC (\hk -> install (h hk))
where h hk (Excep xv hc xk) =
let hc' exc' = capture (h hk) (hc exc')
xk' v = capture (h hk) (xk v) >>= raise
passOn endure =
if endure
then raise (Excep xv hc' xk')
else raise (Excep xv hc xk')
resume endure =
if endure
then xk' () -- might want other than (),
else xk () >>= raise -- say, for implementing state
continue endure =
if endure
then install hc' >>= hk >>= raise
else install hc >>= hk >>= raise
in f xv passOn resume continue
-- | The catching version of trap. It catches exceptions from
-- an expression, instead of from its continuation.
catchC :: Monad m
=> ExceptionHandler o m -- ^ exception handler
-> CPST (Excep o m ()) m (Excep o m ()) -- ^ computation in which to catch exceptions
-> CPST (Excep o m ()) m (Excep o m ())
catchC f m = callCC (\hk -> capture (h hk) m)
where h hk (Excep xv hc xk) =
let hc' exc' = capture (h hk) (hc exc')
xk' v = capture (h hk) (xk v) >>= hk >>= raise
passOn endure =
if endure
then raise (Excep xv hc' xk')
else raise (Excep xv hc xk')
resume endure =
if endure
then xk' () -- might want other than (),
else xk () >>= raise -- say, for implementing state
continue endure =
if endure
then install hc' >> hk (Excep xv hc' xk') >>= raise
else install hc >> hk (Excep xv hc xk') >>= raise
in f xv passOn resume continue
-- | Like abort, ends in the middle of a program. Can be resumed.
end :: (Monad m, ResultType o) => CPST (Excep o m i) m i
end = raiseCC okValue
-- | Can be added at the end of a @do@-sequence to make the return type
-- match the ultimate return type. It returns OK with a continuation that
-- regenerates itself.
done :: (Monad m, ResultType o) => CPST (Excep o m i) m (Excep o m i)
done = return (Excep okValue return (\_ -> done))
-- | Raises an exception that can't be resumed, and can be used in any context.
die :: (Monad m, ResultType o) => o -> CPST (Excep o m i) m a
die x = raise (Excep x return (error "cannot resume from a die"))
-- Note: getCC might seem like a nice idea, but it's untypable, since the continuation
-- has to take itself as an argument. An incorrect attempt:
--
-- getcc :: CPST o m (Cont m o i)
-- getcc = callCC (\k -> return k)