monad-control- Lift control operations, like exception catching, through monad transformers

PortabilityRequires RankNTypes
MaintainerBas van Dijk <>




This module defines the class MonadTransControl of monad transformers through which control operations can be lifted. Instances are included for all the standard monad transformers from the transformers library except ContT.

idLiftControl and liftLiftControlBase are provided to assist creation of MonadControlIO-like classes (see Control.Monad.IO.Control) based on core monads other than IO.



class MonadTrans t => MonadTransControl t whereSource

MonadTransControl is the class of monad transformers supporting an extra operation liftControl, enabling control operations (functions that use monadic actions as input instead of just output) to be lifted through the transformer.


liftControl :: Monad m => (Run t -> m α) -> t m αSource

liftControl is used to peel off the outer layer of a transformed monadic action, allowing an transformed action t m a to be treated as a base action m a.

More precisely, liftControl captures the monadic state of t at the point where it is bound (in t m), yielding a function of type:

Run t = forall n o b. (Monad n, Monad o) => t n b -> n (t o b)

This function runs a transformed monadic action t n b in the inner monad n using the captured state, and leaves the result t o b in the monad n after all side effects in n have occurred.

This can be used to lift control operations with types such as M a -> M a into the transformed monad t M:

  instance Monad M
  foo :: M a -> M a
  foo' :: (MonadTransControl t, Monad (t M)) => t M a -> t M a
  foo' a = control $ run ->    -- run :: t M a -> M (t M a)
             foo $ run a       -- uses foo :: M (t M a) -> M (t M a)

Instances should satisfy similar laws as the MonadTrans laws:

liftControl . const . return = return
liftControl (const (m >>= f)) = liftControl (const m) >>= liftControl . const . f

Additionally instances should satisfy:

control $ \run -> run t = t

type Run t = forall n o β. (Monad n, Monad o, Monad (t o)) => t n β -> n (t o β)Source

control :: (Monad m, Monad (t m), MonadTransControl t) => (Run t -> m (t m α)) -> t m αSource

An often used composition: control = join . liftControl


idLiftControl :: Monad m => (RunInBase m m -> m α) -> m αSource

idLiftControl acts as the "identity" liftControl operation from a monad m to itself.

idLiftControl f = f $ liftM return

It serves as the base case for a class like MonadControlIO, which allows control operations in some base monad (here IO) to be lifted through arbitrary stacks of zero or more monad transformers in one call. For example, Control.Monad.IO.Control defines:

class MonadIO m => MonadControlIO m where
    liftControlIO :: (RunInBase m IO -> IO b) -> m b
instance MonadControlIO IO where
    liftControlIO = idLiftControl

type RunInBase m base = forall β. m β -> base (m β)Source



:: (MonadTransControl t, Monad (t m), Monad m, Monad base) 
=> ((RunInBase m base -> base α) -> m α)

liftControlBase operation

-> (RunInBase (t m) base -> base α) -> t m α 

liftLiftControlBase is used to compose two liftControl operations: the outer provided by a MonadTransControl instance, and the inner provided as the argument.

It satisfies liftLiftControlBase idLiftControl = liftControl.

It serves as the induction step of a MonadControlIO-like class. For example, Control.Monad.IO.Control defines:

instance MonadControlIO m => MonadControlIO (StateT s m) where
    liftControlIO = liftLiftControlBase liftControlIO

using the MonadTransControl instance of StateT s.

The following shows the recursive structure of liftControlIO applied to a stack of three monad transformers with IO as the base monad: t1 (t2 (t3 IO)) a:

 liftLiftControlBase $
   liftLiftControlBase $
     liftLiftControlBase $
   \f -> liftControl $ \run1 ->     -- Capture state of t1, run1 :: Run t1
           liftControl $ \run2 ->   -- Capture state of t2, run2 :: Run t2
             liftControl $ \run3 -> -- Capture state of t3, run3 :: Run t3
               let run :: RunInBase (t1 (t2 (t3 IO))) IO
                   run = -- Restore state
                         liftM (join . lift) -- :: IO (           t2 (t3 IO) (t1 (t2 (t3 IO)) a))   -> IO (                       t1 (t2 (t3 IO)) a)
                       . liftM (join . lift) -- :: IO (    t3 IO (t2 (t3 IO) (t1 (t2 (t3 IO)) a)))  -> IO (           t2 (t3 IO) (t1 (t2 (t3 IO)) a))
                         -- Identity conversion
                       . liftM (join . lift) -- :: IO (IO (t3 IO (t2 (t3 IO) (t1 (t2 (t3 IO)) a)))) -> IO (    t3 IO (t2 (t3 IO) (t1 (t2 (t3 IO)) a)))
                       . liftM return        -- :: IO (    t3 IO (t2 (t3 IO) (t1 (t2 (t3 IO)) a)))  -> IO (IO (t3 IO (t2 (t3 IO) (t1 (t2 (t3 IO)) a))))
                         -- Run     (computation to run:)                              (inner monad:) (restore computation:)
                       . run3 -- ::         t3 IO  (t2 (t3 IO) (t1 (t2 (t3 IO)) a)) ->        IO      (t3 IO (t2 (t3 IO) (t1 (t2 (t3 IO)) a)))
                       . run2 -- ::     t2 (t3 IO)             (t1 (t2 (t3 IO)) a)  ->     t3 IO             (t2 (t3 IO) (t1 (t2 (t3 IO)) a))
                       . run1 -- :: t1 (t2 (t3 IO))                             a   -> t2 (t3 IO)                        (t1 (t2 (t3 IO)) a)
               in f run