monad-control 1.0.2.1 → 1.0.2.2
raw patch · 4 files changed
+201/−29 lines, 4 files
Files
- CHANGELOG +5/−0
- Control/Monad/Trans/Control.hs +195/−25
- README.markdown +0/−3
- monad-control.cabal +1/−1
CHANGELOG view
@@ -1,3 +1,8 @@+1.0.2.2++* Added some good documentation. Courtesy of Franz Thoma.++ 1.0.2.1 * Refer to Michael Snoyman's excellent tutorial on monad-control.
Control/Monad/Trans/Control.hs view
@@ -35,6 +35,33 @@ 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@:++* Define instances @'MonadTransControl' T@ for all transformers @T@, using the+ @'defaultLiftWith'@ and @'defaultRestoreT'@ functions on the constructor and+ deconstructor of @T@.++* Define an instance @'MonadBaseControl' B B@ for the base monad:++ @+ instance MonadBaseControl B B where+ type StM B a = a+ liftBaseWith f = f 'id'+ restoreM = 'return'+ @++* Define instances @'MonadBaseControl' B m => 'MonadBaseControl' B (T m)@ for+ all transformers:++ @+ instance MonadBaseControl b m => MonadBaseControl b (T m) where+ type StM (T m) a = 'ComposeSt' T m a+ liftBaseWith f = 'defaultLiftBaseWith'+ restoreM = 'defaultRestoreM'+ @ -} module Control.Monad.Trans.Control@@ -44,6 +71,7 @@ -- ** Defaults -- $MonadTransControlDefaults , RunDefault, defaultLiftWith, defaultRestoreT+ -- *** Defaults for a stack of two -- $MonadTransControlDefaults2 , RunDefault2, defaultLiftWith2, defaultRestoreT2 @@ -121,13 +149,59 @@ -- MonadTransControl type class -------------------------------------------------------------------------------- +-- | 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+-- @ class MonadTrans t => MonadTransControl t where -- | Monadic state of @t@. --- -- For clarity, because haddock does not display associated types, below are- -- the elaborated 'StT' definitions provided by this library:+ -- The monadic state of a monad transformer is the result type of its @run@+ -- function, e.g.: -- -- @+ -- 'runReaderT' :: 'ReaderT' r m a -> r -> m a+ -- 'StT' ('ReaderT' r) a ~ a+ --+ -- 'runStateT' :: 'StateT' s m a -> s -> m (a, s)+ -- 'StT' ('StateT' s) a ~ (a, s)+ --+ -- 'runMaybeT' :: 'MaybeT' m a -> m ('Maybe' a)+ -- 'StT' 'MaybeT' a ~ 'Maybe' a+ -- @+ --+ -- Provided type instances:+ --+ -- @ -- StT 'IdentityT' a ~ a -- StT 'MaybeT' a ~ 'Maybe' a -- StT ('ErrorT' e) a ~ 'Error' e => 'Either' e a@@ -152,7 +226,29 @@ -- 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.+ -- @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 (const 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'))+ -- @ liftWith :: Monad m => (Run t -> m a) -> t m a -- | Construct a @t@ computation from the monadic state of @t@ that is@@ -162,6 +258,23 @@ -- -- @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: -- -- @@@ -197,6 +310,14 @@ -- 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'+-- @ type Run t = forall n b. Monad n => t n b -> n (StT t b) @@ -207,8 +328,8 @@ -- $MonadTransControlDefaults -- -- The following functions can be used to define a 'MonadTransControl' instance--- for a monad transformer which simply wraps another monad transformer which--- already has a @MonadTransControl@ instance. For example:+-- for a monad transformer which simply is a newtype around another monad+-- transformer which already has a @MonadTransControl@ instance. For example: -- -- @ -- {-\# LANGUAGE GeneralizedNewtypeDeriving \#-}@@ -389,10 +510,18 @@ -- MonadBaseControl type class -------------------------------------------------------------------------------- +-- |+-- == 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'@. class MonadBase b m => MonadBaseControl b m | m -> b where- -- | Monadic state of @m@.+ -- | Monadic state that @m@ adds to the base monad @b@. --- -- For all non-transformer monads, @StM m a ~ a@:+ -- For all base (non-transformed) monads, @StM m a ~ a@: -- -- @ -- StM 'IO' a ~ a@@ -405,20 +534,20 @@ -- StM ('ST' s) a ~ a -- @ --- -- All transformer monads\' 'StM' depends on both the monadic state of the- -- transformer (given by its 'StT' from 'MonadTransControl'), as well as its- -- inner monad's monadic state, given by its 'StM' from 'MonadBaseControl':+ -- If @m@ is a transformed monad, @m ~ t b@, @'StM'@ is the monadic state of+ -- the transformer @t@ (given by its 'StT' from 'MonadTransControl'). For a+ -- transformer stack, @'StM'@ is defined recursively: -- -- @- -- StM ('IdentityT' m) a ~ StM m a- -- StM ('MaybeT' m) a ~ StM m ('Maybe' a)- -- StM ('ErrorT' e m) a ~ 'Error' e => StM m ('Either' e a)- -- StM ('ExceptT' e m) a ~ StM m ('Either' e a)- -- StM ('ListT' m) a ~ StM m [a]- -- StM ('ReaderT' r m) a ~ StM m a- -- StM ('StateT' s m) a ~ StM m (a, s)- -- StM ('WriterT' w m) a ~ 'Monoid' w => StM m (a, w)- -- StM ('RWST' r w s m) a ~ 'Monoid' w => StM m (a, s, w)+ -- StM ('IdentityT' m) a ~ 'ComposeSt' 'IdentityT' m a ~ StM m a+ -- StM ('MaybeT' m) a ~ 'ComposeSt' 'MaybeT' m a ~ StM m ('Maybe' a)+ -- StM ('ErrorT' e m) a ~ 'ComposeSt' 'ErrorT' m a ~ 'Error' e => StM m ('Either' e a)+ -- StM ('ExceptT' e m) a ~ 'ComposeSt' 'ExceptT' m a ~ StM m ('Either' e a)+ -- StM ('ListT' m) a ~ 'ComposeSt' 'ListT' m a ~ StM m [a]+ -- StM ('ReaderT' r m) a ~ 'ComposeSt' 'ReaderT' m a ~ StM m a+ -- StM ('StateT' s m) a ~ 'ComposeSt' 'StateT' m a ~ StM m (a, s)+ -- StM ('WriterT' w m) a ~ 'ComposeSt' 'WriterT' m a ~ 'Monoid' w => StM m (a, w)+ -- StM ('RWST' r w s m) a ~ 'ComposeSt' 'RWST' m a ~ 'Monoid' w => StM m (a, s, w) -- @ type StM m a :: * @@ -434,7 +563,17 @@ -- 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.+ -- 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'@. liftBaseWith :: (RunInBase m b -> b a) -> m a -- | Construct a @m@ computation from the monadic state of @m@ that is@@ -443,6 +582,9 @@ -- Instances should satisfy: -- -- @liftBaseWith (\\runInBase -> runInBase m) >>= restoreM = m@+ --+ -- @'restoreM'@ is usually not implemented directly, but using+ -- @'defaultRestoreM'@. restoreM :: StM m a -> m a -- | A function that runs a @m@ computation on the monadic state that was@@ -465,6 +607,8 @@ -- 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@. type RunInBase m b = forall a. m a -> b (StM m a) @@ -593,6 +737,16 @@ -------------------------------------------------------------------------------- -- | 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))+-- @ control :: MonadBaseControl b m => (RunInBase m b -> b (StM m a)) -> m a control f = liftBaseWith f >>= restoreM {-# INLINABLE control #-}@@ -622,11 +776,15 @@ -- | @liftBaseOp@ is a particular application of 'liftBaseWith' that allows -- lifting control operations of type: ----- @((a -> b c) -> b c)@ to: @('MonadBaseControl' b m => (a -> m c) -> m c)@.+-- @((a -> b c) -> b c)@ --+-- to:+--+-- @('MonadBaseControl' b m => (a -> m c) -> m c)@+-- -- For example: ----- @liftBaseOp alloca :: 'MonadBaseControl' 'IO' m => (Ptr a -> m c) -> m c@+-- @liftBaseOp alloca :: (Storable a, 'MonadBaseControl' 'IO' m) => (Ptr a -> m c) -> m c@ liftBaseOp :: MonadBaseControl b m => ((a -> b (StM m c)) -> b (StM m d)) -> ((a -> m c) -> m d)@@ -636,8 +794,12 @@ -- | @liftBaseOp_@ is a particular application of 'liftBaseWith' that allows -- lifting control operations of type: ----- @(b a -> b a)@ to: @('MonadBaseControl' b m => m a -> m a)@.+-- @(b a -> b a)@ --+-- to:+--+-- @('MonadBaseControl' b m => m a -> m a)@+-- -- For example: -- -- @liftBaseOp_ mask_ :: 'MonadBaseControl' 'IO' m => m a -> m a@@@ -650,8 +812,12 @@ -- | @liftBaseDiscard@ is a particular application of 'liftBaseWith' that allows -- lifting control operations of type: ----- @(b () -> b a)@ to: @('MonadBaseControl' b m => m () -> m a)@.+-- @(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@.@@ -666,7 +832,11 @@ -- | @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)@.+-- @((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
README.markdown view
@@ -12,9 +12,6 @@ operators and exploits the `RankNTypes` language extension to simplify most definitions. -The package includes a copy of the `monad-peel` testsuite written by-Anders Kaseorg The tests can be performed by using `cabal test`.- [This `criterion`](https://github.com/basvandijk/bench-monad-peel-control) based benchmark shows that `monad-control` is on average about 2.5 times faster than `monad-peel`.
monad-control.cabal view
@@ -1,5 +1,5 @@ Name: monad-control-Version: 1.0.2.1+Version: 1.0.2.2 Synopsis: Lift control operations, like exception catching, through monad transformers License: BSD3 License-file: LICENSE