HORIZON HASKELLDocslts/ghc-9.10.x248f8f02026-10-05Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · 248f8f0 · 2026-10-05

Modulemonad-control-1.0.3.1Haskell2010

Control.Monad.Trans.Control

This module defines the type class MonadBaseControl, a subset of MonadBase into which generic control operations such as catch can be lifted from IO or any other base monad. Instances are based on monad transformers in MonadTransControl, which includes all standard monad transformers in the transformers library except ContT.

See the lifted-base package which uses monad-control to lift IO operations from the base library (like catch or bracket) into any monad that is an instance of MonadBase or MonadBaseControl.

See the following tutorial by Michael Snoyman on how to use this package:

https://www.yesodweb.com/book/monad-control

Quick implementation guide

Given a base monad B and a stack of transformers T:

  • 6 types
  • 2 classes
  • 17 values

MonadTransControl

2 declarations
classclass MonadTrans t => MonadTransControl (t :: (Type -> Type) -> Type -> Type) where
#

The MonadTransControl type class is a stronger version of MonadTrans:

Instances of MonadTrans know how to lift actions in the base monad to the transformed monad. These lifted actions, however, are completely unaware of the monadic state added by the transformer.

MonadTransControl instances are aware of the monadic state of the transformer and allow to save and restore this state.

This allows to lift functions that have a monad transformer in both positive and negative position. Take, for example, the function

withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r

MonadTrans instances can only lift the return type of the withFile function:

withFileLifted :: MonadTrans t => FilePath -> IOMode -> (Handle -> IO r) -> t IO r
withFileLifted file mode action = lift (withFile file mode action)

However, MonadTrans is not powerful enough to make withFileLifted accept a function that returns t IO. The reason is that we need to take away the transformer layer in order to pass the function to withFile. MonadTransControl allows us to do this:

withFileLifted' :: (Monad (t IO), MonadTransControl t) => FilePath -> IOMode -> (Handle -> t IO r) -> t IO r
withFileLifted' file mode action = liftWith (\run -> withFile file mode (run . action)) >>= restoreT . return

Associated types

Methods

  • liftWith :: Monad m => (Run t -> m a) -> t m a

    liftWith is similar to lift in that it lifts a computation from the argument monad to the constructed monad.

    Instances should satisfy similar laws as the MonadTrans laws:

    liftWith (\_ -> return a) = return a
    liftWith (\_ -> m >>= f)  =  liftWith (\_ -> m) >>= (\a -> liftWith (\_ -> f a))

    The difference with lift is that before lifting the m computation liftWith captures the state of t. It then provides the m computation with a Run function that allows running t n computations in n (for all n) on the captured state, e.g.

    withFileLifted :: (Monad (t IO), MonadTransControl t) => FilePath -> IOMode -> (Handle -> t IO r) -> t IO r
    withFileLifted file mode action = liftWith (\run -> withFile file mode (run . action)) >>= restoreT . return
    

    If the Run function is ignored, liftWith coincides with lift:

    lift f = liftWith (\_ -> f)

    Implementations use the Run function associated with a transformer:

    liftWith :: Monad m => ((Monad n => ReaderT r n b -> n b) -> m a) -> ReaderT r m a
    liftWith f = ReaderT (\r -> f (\action -> runReaderT action r))
    
    liftWith :: Monad m => ((Monad n => StateT s n b -> n (b, s)) -> m a) -> StateT s m a
    liftWith f = StateT (\s -> liftM (\x -> (x, s)) (f (\action -> runStateT action s)))
    
    liftWith :: Monad m => ((Monad n => MaybeT n b -> n (Maybe b)) -> m a) -> MaybeT m a
    liftWith f = MaybeT (liftM Just (f runMaybeT))
    
  • restoreT :: Monad m => m (StT t a) -> t m a

    Construct a t computation from the monadic state of t that is returned from a Run function.

    Instances should satisfy:

    liftWith (\run -> run t) >>= restoreT . return = t

    restoreT is usually implemented through the constructor of the monad transformer:

    ReaderT  :: (r -> m a) -> ReaderT r m a
    restoreT ::       m a  -> ReaderT r m a
    restoreT action = ReaderT { runReaderT = const action }
    
    StateT   :: (s -> m (a, s)) -> StateT s m a
    restoreT ::       m (a, s)  -> StateT s m a
    restoreT action = StateT { runStateT = const action }
    
    MaybeT   :: m (Maybe a) -> MaybeT m a
    restoreT :: m (Maybe a) -> MaybeT m a
    restoreT action = MaybeT action
    

    Example type signatures:

    restoreT :: Monad m             => m a            -> IdentityT m a
    restoreT :: Monad m             => m (Maybe a)    -> MaybeT m a
    restoreT :: (Monad m, Error e)  => m (Either e a) -> ErrorT e m a
    restoreT :: Monad m             => m (Either e a) -> ExceptT e m a
    restoreT :: Monad m             => m [a]          -> ListT m a
    restoreT :: Monad m             => m a            -> ReaderT r m a
    restoreT :: Monad m             => m (a, s)       -> StateT s m a
    restoreT :: (Monad m, Monoid w) => m (a, w)       -> WriterT w m a
    restoreT :: (Monad m, Monoid w) => m (a, s, w)    -> RWST r w s m a
    
Instances10MonadTransControl, …
typetype Run (t :: (Type -> Type) -> Type -> Type) = forall (n :: Type -> Type) b. Monad n => t n b -> n (StT t b)
#

A function that runs a transformed monad t n on the monadic state that was captured by liftWith

A Run t function yields a computation in n that returns the monadic state of t. This state can later be used to restore a t computation using restoreT.

Example type equalities:

Run IdentityT    ~ forall n b. Monad n             => IdentityT  n b -> n b
Run MaybeT       ~ forall n b. Monad n             => MaybeT     n b -> n (Maybe b)
Run (ErrorT e)   ~ forall n b. (Monad n, Error e)  => ErrorT e   n b -> n (Either e b)
Run (ExceptT e)  ~ forall n b. Monad n             => ExceptT e  n b -> n (Either e b)
Run ListT        ~ forall n b. Monad n             => ListT      n b -> n [b]
Run (ReaderT r)  ~ forall n b. Monad n             => ReaderT r  n b -> n b
Run (StateT s)   ~ forall n b. Monad n             => StateT s   n b -> n (a, s)
Run (WriterT w)  ~ forall n b. (Monad n, Monoid w) => WriterT w  n b -> n (a, w)
Run (RWST r w s) ~ forall n b. (Monad n, Monoid w) => RWST r w s n b -> n (a, s, w)

This type is usually satisfied by the run function of a transformer:

flip runReaderT :: r -> Run (ReaderT r)
flip runStateT  :: s -> Run (StateT s)
runMaybeT       ::      Run MaybeT

Defaults

The following functions can be used to define a MonadTransControl instance for a monad transformer which simply is a newtype around another monad transformer which already has a MonadTransControl instance. For example:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeFamilies #-}

newtype CounterT m a = CounterT {unCounterT :: StateT Int m a}
  deriving (Monad, MonadTrans)

instance MonadTransControl CounterT where
    type StT CounterT a = StT (StateT Int) a
    liftWith = defaultLiftWith CounterT unCounterT
    restoreT = defaultRestoreT CounterT

Defaults for a stack of two

The following functions can be used to define a MonadTransControl instance for a monad transformer stack of two.

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype CalcT m a = CalcT { unCalcT :: StateT Int (ExceptT String m) a }
  deriving (Monad, MonadTrans)

instance MonadTransControl CalcT where
    type StT CalcT a = StT (ExceptT String) (StT (StateT Int) a)
    liftWith = defaultLiftWith2 CalcT unCalcT
    restoreT = defaultRestoreT2 CalcT

MonadBaseControl

2 declarations
classclass MonadBase b m => MonadBaseControl (b :: Type -> Type) (m :: Type -> Type) | m -> b where
#

Writing instances

The usual way to write a MonadBaseControl instance for a transformer stack over a base monad B is to write an instance MonadBaseControl B B for the base monad, and MonadTransControl T instances for every transformer T. Instances for MonadBaseControl are then simply implemented using ComposeSt, defaultLiftBaseWith, defaultRestoreM.

Associated types

Methods

  • liftBaseWith :: (RunInBase m b -> b a) -> m a

    liftBaseWith is similar to liftIO and liftBase in that it lifts a base computation to the constructed monad.

    Instances should satisfy similar laws as the MonadIO and MonadBase laws:

    liftBaseWith (\_ -> return a) = return a
    liftBaseWith (\_ -> m >>= f)  =  liftBaseWith (\_ -> m) >>= (\a -> liftBaseWith (\_ -> f a))

    As Li-yao Xia explains, parametricity guarantees that

    f $ liftBaseWith q = liftBaseWith $ runInBase -> f $ q runInBase

    The difference with liftBase is that before lifting the base computation liftBaseWith captures the state of m. It then provides the base computation with a RunInBase function that allows running m computations in the base monad on the captured state:

    withFileLifted :: MonadBaseControl IO m => FilePath -> IOMode -> (Handle -> m a) -> m a
    withFileLifted file mode action = liftBaseWith (\runInBase -> withFile file mode (runInBase . action)) >>= restoreM
                                 -- = control $ \runInBase -> withFile file mode (runInBase . action)
                                 -- = liftBaseOp (withFile file mode) action
    

    liftBaseWith is usually not implemented directly, but using defaultLiftBaseWith.

  • restoreM :: StM m a -> m a

    Construct a m computation from the monadic state of m that is returned from a RunInBase function.

    Instances should satisfy:

    liftBaseWith (\runInBase -> runInBase m) >>= restoreM = m

    restoreM is usually not implemented directly, but using defaultRestoreM.

Instances19MonadBaseControl, …
typetype RunInBase (m :: Type -> Type) (b :: Type -> Type) = forall a. m a -> b (StM m a)
#

A function that runs a m computation on the monadic state that was captured by liftBaseWith

A RunInBase m function yields a computation in the base monad of m that returns the monadic state of m. This state can later be used to restore the m computation using restoreM.

Example type equalities:

RunInBase (IdentityT  m) b ~ forall a.             IdentityT  m a -> b (StM m a)
RunInBase (MaybeT     m) b ~ forall a.             MaybeT     m a -> b (StM m (Maybe a))
RunInBase (ErrorT e   m) b ~ forall a. Error e =>  ErrorT e   m a -> b (StM m (Either e a))
RunInBase (ExceptT e  m) b ~ forall a.             ExceptT e  m a -> b (StM m (Either e a))
RunInBase (ListT      m) b ~ forall a.             ListT      m a -> b (StM m [a])
RunInBase (ReaderT r  m) b ~ forall a.             ReaderT    m a -> b (StM m a)
RunInBase (StateT s   m) b ~ forall a.             StateT s   m a -> b (StM m (a, s))
RunInBase (WriterT w  m) b ~ forall a. Monoid w => WriterT w  m a -> b (StM m (a, w))
RunInBase (RWST r w s m) b ~ forall a. Monoid w => RWST r w s m a -> b (StM m (a, s, w))

For a transformed base monad m ~ t b, 'RunInBase m b' ~ Run t.

Defaults

Note that by using the following default definitions it's easy to make a monad transformer T an instance of MonadBaseControl:

instance MonadBaseControl b m => MonadBaseControl b (T m) where
    type StM (T m) a = ComposeSt T m a
    liftBaseWith     = defaultLiftBaseWith
    restoreM         = defaultRestoreM

Defining an instance for a base monad B is equally straightforward:

instance MonadBaseControl B B where
    type StM B a   = a
    liftBaseWith f = f id
    restoreM       = return

Utility functions

11 declarations
valuecontrol :: MonadBaseControl b m => (RunInBase m b -> b (StM m a)) -> m a
#

An often used composition: control f = liftBaseWith f >>= restoreM

Example:

liftedBracket :: MonadBaseControl IO m => m a -> (a -> m b) -> (a -> m c) -> m c
liftedBracket acquire release action = control $ \runInBase ->
    bracket (runInBase acquire)
            (\saved -> runInBase (restoreM saved >>= release))
            (\saved -> runInBase (restoreM saved >>= action))
valueembed :: MonadBaseControl b m => (a -> m c) -> m (a -> b (StM m c))
#

Embed a transformer function as an function in the base monad returning a mutated transformer state.

valueembed_ :: MonadBaseControl b m => (a -> m ()) -> m (a -> b ())
#

Performs the same function as embed, but discards transformer state from the embedded function.

valueliftBaseDiscard :: MonadBaseControl b m => (b () -> b a) -> m () -> m a
#

liftBaseDiscard is a particular application of liftBaseWith that allows lifting control operations of type:

(b () -> b a)

to:

(MonadBaseControl b m => m () -> m a)

Note that, while the argument computation m () has access to the captured state, all its side-effects in m are discarded. It is run only for its side-effects in the base monad b.

For example:

liftBaseDiscard forkIO :: MonadBaseControl IO m => m () -> m ThreadId
valueliftBaseOpDiscard
  1. :: MonadBaseControl b m
  2. => (a -> b ()) -> b c
  3. -> a -> m ()
  4. -> m c
#

liftBaseOpDiscard is a particular application of liftBaseWith that allows lifting control operations of type:

((a -> b ()) -> b c)

to:

(MonadBaseControl b m => (a -> m ()) -> m c)

Note that, while the argument computation m () has access to the captured state, all its side-effects in m are discarded. It is run only for its side-effects in the base monad b.

For example:

liftBaseDiscard (runServer addr port) :: MonadBaseControl IO m => m () -> m ()