liveBook · Manning

StateT

As explained in Functional Design and Architecture: Examples in Haskell

StateT is a Haskell monad transformer that adds a mutable-state abstraction to an underlying monadic computation.

StateT adds a threaded, pseudo-mutable state to an existing monadic computation. Instead of passing an updated model through every function explicitly, a script can read the current value with get, replace it with put, and still use the effects supplied by an underlying monad. This keeps stateful steps together while preserving an explicit execution boundary: running the computation requires an initial state and yields a result within the wrapped monad. section 7.2.2 section 7.2.3

Its shape is StateT state internalMonad result. The outer layer manages one state value; the inner monad remains available for its own capabilities. For example, StateT Board IO can update a game board and print messages. StateT is itself a monad, so ordinary do-notation sequences state reads, state updates, and lifted inner actions. section 7.2.3 section 7.2.3

Listing 7.6 Monad stack for a Minesweeper game
import Control.Monad.State (StateT)
  
data CellType                               #1
  = Bomb
  | Num Int
  | Empty
  
data Cell                                   #2
  = Hidden CellType
  | Revealed CellType
  
type Board = Map (X, Y) Cell                #3
  
type BoardMonad a = StateT Board IO a       #4

A board type alias makes the division of responsibilities visible: Board is the threaded state, while IO supplies console interaction. section 7.2.3

The key distinction from State is not how state is accessed—both use get and put—but what can happen alongside it. A State computation is pure state alone. StateT wraps another monad, so an action from that lower layer must be adapted into the StateT layer with lift. In a board-revealing action, get obtains the board, put stores a changed board, and lift (putStrLn ...) performs console output without abandoning the stateful script. section 7.2.2 section 7.2.3

Reveal a Minesweeper cell

A reveal operation first obtains the Board with get, checks the requested coordinates, and updates a hidden cell with put when appropriate. It uses lifted putStrLn calls to report missing, already revealed, bomb, or successful cases. The state transition stays local to the operation, while observable IO is clearly marked as belonging to the underlying layer. section 7.2.3

Listing 7.7 Lifting of IO actions
import Control.Monad.State (get, put, lift)
  
revealCell :: (X, Y) -> BoardMonad ()
revealCell (x, y) = do
  board <- get
  
  lift (putStrLn ("Trying cell " <> show (x, y)))           #1
  
  case Map.lookup (x, y) board of
    Nothing            -> lift (putStrLn "No such cell.")
    Just (Revealed _)  -> lift (putStrLn "Already revealed.")
    Just (Hidden Bomb) -> lift (putStrLn "It's bomb! You loose!")
    Just (Hidden c)    -> do
      let newBoard = Map.insert (x, y) (Revealed c) board
      put newBoard
      lift (putStrLn "Cell revealed.")

The listing contrasts direct state operations with IO operations that need lift, making the boundary between the two layers concrete. section 7.2.3

Stacks can add more than one StateT layer when separate state contexts deserve separate ownership. A game stack may keep Score at the top and Board beneath it. The nearest layer is accessed directly; reaching the lower board requires lift get, and reaching IO requires lift twice. This separation can model distinct concerns, but deeper stacks also add lifting ceremony. section 7.2.3 section 7.2.3

StateT computations are executed with runStateT, execStateT, or evalStateT. runStateT returns both result and final state inside the underlying monad; execStateT keeps only state; evalStateT keeps only the result. For a stacked computation, layers are run one at a time: a game state action can become a board action, then an IO action. That staged execution also supports tests that assert the resulting board. section 7.2.3 section 7.2.3

Listing 7.9 Running the monad stack in tests
board1 :: Board
board1 = Map.fromList
  [ ( (1, 1), Hidden Empty )
  , ( (2, 2), Hidden Bomb  )
  ]
  
board2 :: Board
board2 = Map.fromList
  [ ( (1, 1), Revealed Empty )
  , ( (2, 2), Hidden Bomb  )
  ]
  
spec :: Spec
spec =
  describe "StateT monad tests" $ do
    it "BoardMonad stack" $ do
      let runBoard = runStateT (revealCell (1, 1)) board1        #1
      (_, board') <- runBoard                                    #2
      board' `shouldBe` board2
  
    it "MinesweeperGame stack" $ do
      let runGame  :: BoardMonad Score = execStateT printGame 0  #1
      let runBoard :: IO Board = execStateT runGame board1       #1
  
      board' <- runBoard                                         #2
      board' `shouldBe` board1

The tests run a BoardMonad and a two-state game stack, then check the final board after each layer is executed. section 7.2.3

StateT can also add model state to a domain-specific monad. A ship-control script uses StateT SpaceshipModel LogicControl so rotation code reads the current model directly while lifting property lookups and validation from LogicControl. The resulting action can later be evaluated back into LogicControl and executed to produce the changed model. When the underlying monad is a type synonym, wrapping it in a newtype may be necessary before using it as a transformer layer. section 7.2.3 section 7.2.3

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Explains types, get, put, lift, nested stacks, runners, and tests; defers transformer foundations to appendixes.
Examples
Uses aging scripts, a Minesweeper board reveal, layered score-and-board state, tests, and spaceship rotation control.
What it assumes
Assumes familiarity with monads and do-notation while introducing StateT as a wrapper around another monad.
What you can do after
Build stateful scripts over IO or a custom monad, lift inner actions, run stack layers, and test final state.
How it connects
Connects StateT with State, IO, get, put, lift, do-notation, Free monads, and monad transformers.

Also explained in

Compare how 2 books explain StateT →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Functional Design and Architecture: Examples in Haskell. The section links point to the parts of the book it draws on.