🎓 A1Learner
🔍
0 of 47 lessons completed

Haskell MTL Stack

An MTL stack is a type of layered monads that provides several class operations at once. Each layer adds one capability, and the layers are peeled in order to run the stack.

A stack reads and changes state

Ask reads the environment 10, and modify adds it to the state. The value and the final state are both 10.

import Control.Monad.Reader
import Control.Monad.State

prog :: ReaderT Int (StateT Int IO) Int
prog = do
  e <- ask
  modify (+ e)
  get

main :: IO ()
main = do
  result <- runStateT (runReaderT prog 10) 0
  print result

Local works inside a stack

Local changes the environment for one ask inside the stack. The state still records only the outer environment.

import Control.Monad.Reader
import Control.Monad.State

prog :: ReaderT Int (StateT Int IO) (Int, Int, Int)
prog = do
  a <- ask
  b <- local (+ 5) ask
  modify (+ a)
  s <- get
  return (a, b, s)

main :: IO ()
main = do
  result <- runStateT (runReaderT prog 10) 0
  print result

LiftIO runs plain IO in a stack

LiftIO brings a plain IO value into the stack. Adding it to the environment gives 17.

import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.IO.Class

prog :: ReaderT Int (StateT Int IO) Int
prog = do
  e <- ask
  x <- liftIO (return 7)
  return (e + x)

main :: IO ()
main = do
  result <- runStateT (runReaderT prog 10) 0
  print result

Example

import Control.Monad.Reader
import Control.Monad.State

prog :: ReaderT Int (StateT Int IO) (Int, Int)
prog = do
  bonus <- ask
  modify (+ bonus)
  modify (* 2)
  total <- get
  bumped <- local (+ 3) ask
  return (total, bumped)

main :: IO ()
main = do
  result <- runStateT (runReaderT prog 5) 10
  print result

Key points

  • Layers: ReaderT adds an environment layer and StateT adds a state layer.
  • Order: runReaderT peels the environment layer and runStateT peels the state layer.
  • Ask in a stack: ask still reads the environment inside a larger stack.
  • Modify in a stack: modify still changes the state inside a larger stack.
  • LiftIO: liftIO runs a plain IO action inside a stack built on IO.

In this example, ask gives the bonus 5, modify adds it and doubles the state to 30, and local adds 3 to the environment for the final ask to give 8.

Note

the run order must match the layer order, or the types do not fit.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Haskell Playground

Exercise

For ReaderT Int (StateT Int IO), which runner peels the environment layer first?