Copyright | (c) Andy Gill 2001 (c) Oregon Graduate Institute of Science and Technology 2001 |
---|---|
License | BSD-style (see the file LICENSE) |
Maintainer | [email protected] |
Stability | experimental |
Portability | portable |
Safe Haskell | Safe |
Language | Haskell98 |
The class of monad transformers.
A monad transformer makes a new monad out of an existing monad, such that computations of the old monad may be embedded in the new one. To construct a monad with a desired set of features, one typically starts with a base monad, such as Identity
, []
or IO
, and applies a sequence of monad transformers.
class MonadTrans t where Source
The class of monad transformers. Instances should satisfy the following laws, which state that lift
is a monad transformation:
lift :: Monad m => m a -> t m a Source
Lift a computation from the argument monad to the constructed monad.
Most monad transformer modules include the special case of applying the transformer to Identity
. For example, State s
is an abbreviation for StateT s Identity
.
Each monad transformer also comes with an operation run
XXXT
to unwrap the transformer, exposing a computation of the inner monad. (Currently these functions are defined as field labels, but in the next major release they will be separate functions.)
All of the monad transformers except ContT
and SelectT
are functors on the category of monads: in addition to defining a mapping of monads, they also define a mapping from transformations between base monads to transformations between transformed monads, called map
XXXT
. Thus given a monad transformation t :: M a -> N a
, the combinator mapStateT
constructs a monad transformation
mapStateT t :: StateT s M a -> StateT s N a
For these monad transformers, lift
is a natural transformation in the category of monads, i.e. for any monad transformation t :: M a -> N a
,
Each of the monad transformers introduces relevant operations. In a sequence of monad transformers, most of these operations.can be lifted through other transformers using lift
or the map
XXXT
combinator, but a few with more complex type signatures require specialized lifting combinators, called lift
Op (see Control.Monad.Signatures).
A monad is said to be strict if its >>=
operation is strict in its first argument. The base monads Maybe
, []
and IO
are strict:
>>> undefined >> return 2 :: Maybe Integer *** Exception: Prelude.undefined
However the monad Identity
is not:
>>> runIdentity (undefined >> return 2) 2
In a strict monad you know when each action is executed, but the monad is not necessarily strict in the return value, or in other components of the monad, such as a state. However you can use seq
to create an action that is strict in the component you want evaluated.
The first example is a parser monad in the style of
We can define such a parser monad by adding a state (the String
remaining to be parsed) to the []
monad, which provides non-determinism:
import Control.Monad.Trans.State type Parser = StateT String []
Then Parser
is an instance of MonadPlus
: monadic sequencing implements concatenation of parsers, while mplus
provides choice. To use parsers, we need a primitive to run a constructed parser on an input string:
runParser :: Parser a -> String -> [a] runParser p s = [x | (x, "") <- runStateT p s]
Finally, we need a primitive parser that matches a single character, from which arbitrarily complex parsers may be constructed:
item :: Parser Char item = do c:cs <- get put cs return c
In this example we use the operations get
and put
from Control.Monad.Trans.State, which are defined only for monads that are applications of StateT
. Alternatively one could use monad classes from the mtl
package or similar, which contain methods get
and put
with types generalized over all suitable monads.
We can define a parser that also counts by adding a WriterT
transformer:
import Control.Monad.Trans.Class import Control.Monad.Trans.State import Control.Monad.Trans.Writer import Data.Monoid type Parser = WriterT (Sum Int) (StateT String [])
The function that applies a parser must now unwrap each of the monad transformers in turn:
runParser :: Parser a -> String -> [(a, Int)] runParser p s = [(x, n) | ((x, Sum n), "") <- runStateT (runWriterT p) s]
To define the item
parser, we need to lift the StateT
operations through the WriterT
transformer.
item :: Parser Char item = do c:cs <- lift get lift (put cs) return c
In this case, we were able to do this with lift
, but operations with more complex types require special lifting functions, which are provided by monad transformers for which they can be implemented. If you use the monad classes of the mtl
package or similar, this lifting is handled automatically by the instances of the classes, and you need only use the generalized methods get
and put
.
We can also define a primitive using the Writer:
tick :: Parser () tick = tell (Sum 1)
Then the parser will keep track of how many tick
s it executes.
This example is a cut-down version of the one in
Suppose we want to define an interpreter that can do I/O and has exceptions, an environment and a modifiable store. We can define a monad that supports all these things as a stack of monad transformers:
import Control.Monad.Trans.Class import Control.Monad.Trans.State import qualified Control.Monad.Trans.Reader as R import qualified Control.Monad.Trans.Except as E import Control.Monad.IO.Class type InterpM = StateT Store (R.ReaderT Env (E.ExceptT Err IO))
for suitable types Store
, Env
and Err
.
Now we would like to be able to use the operations associated with each of those monad transformers on InterpM
actions. Since the uppermost monad transformer of InterpM
is StateT
, it already has the state operations get
and set
.
The first of the ReaderT
operations, ask
, is a simple action, so we can lift it through StateT
to InterpM
using lift
:
ask :: InterpM Env ask = lift R.ask
The other ReaderT
operation, local
, has a suitable type for lifting using mapStateT
:
local :: (Env -> Env) -> InterpM a -> InterpM a local f = mapStateT (R.local f)
We also wish to lift the operations of ExceptT
through both ReaderT
and StateT
. For the operation throwE
, we know throwE e
is a simple action, so we can lift it through the two monad transformers to InterpM
with two lift
s:
throwE :: Err -> InterpM a throwE e = lift (lift (E.throwE e))
The catchE
operation has a more complex type, so we need to use the special-purpose lifting function liftCatch
provided by most monad transformers. Here we use the ReaderT
version followed by the StateT
version:
catchE :: InterpM a -> (Err -> InterpM a) -> InterpM a catchE = liftCatch (R.liftCatch E.catchE)
We could lift IO
actions to InterpM
using three lift
s, but InterpM
is automatically an instance of MonadIO
, so we can use liftIO
instead:
putStr :: String -> InterpM () putStr s = liftIO (Prelude.putStr s)
© The University of Glasgow and others
Licensed under a BSD-style license (see top of the page).
https://downloads.haskell.org/~ghc/8.8.3/docs/html/libraries/transformers-0.5.6.2/Control-Monad-Trans-Class.html