Combining Effects with Monad Transformers
Motivation
- What if we want to have a monad that combines the effects of multiple monads?
- We don't want to write a monad instance for every combination of effects
- State and Either
- State and IO
- ...
Monad Transformers
A monad transformer...
- takes a base monad m
- adds a certain effect to it (for example `State s`)
- and returns a new monad
The resulting monad has both:
- the effect of the base monad m
- and the effect of the transformer
StateT s m
Example
Notes
- We can access the functionality of the base monad (IO) using `lift`
type MyMonad = StateT String IO
algorithm :: MyMonad Int
algorithm = do
oldState <- get
input <- lift getLine
put input
pure (length oldState)
Getting back to the base Monad
runStateT :: StateT s m a -> s -> m (a, s)
Every monad transformer FooT has a runFooT function. For example for StateT:
In our example, we can use that to run the algorithm in the `main` function:
main :: IO ()
main = do
initialState <- getLine
(result, newState) <- runStateT algorithm initialState
print result
Transformer "Stacks"
type MyMonad = StateT String (ReaderT Config IO)
We can repeat the process of applying a monad transformer to combine more than two effects:
StateT String |
ReaderT Config |
IO |
algorithm :: MyMonad Int
algorithm = do
input <- lift (lift getLine)
...
The Stack:
Avoiding all the `lift`s
class MonadReader r m where
ask :: m r
The functionality of the different effects is actually defined in typeclasses, for example:
instance MonadReader r m => MonadReader r (StateT s m) where
ask = lift ask
Transformers typically implement these typeclasses if the base monad implements it:
Avoiding all the `lift`s
type MyMonad = StateT String (ReaderT Config IO)
algorithm :: MyMonad Int
algorithm = do
cfg <- ask
...
So in practice:
Behind the Scenes
How and why do transformers work?
Live coding
Monad Transformers
By Moritz Drexl
Monad Transformers
- 702