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

Made with Slides.com