A state monad organizes pure stateful work without passing an updated model through every function call. It is useful when a deep application model—such as a spaceship’s mass, coordinates, orientation, and controllers—would otherwise produce repetitive record updates, especially alongside error-handling effects. section 7.2.1 section 7.3
Within a State computation, get reads the current state and put replaces it with a new value. Functions share an apparent mutable context in do-notation, but no shared memory is mutated: the monad internally wraps argument-passing state, and bind carries each resulting state into the next computation. section 7.2.2 section 7.3
An age script
An Age state can begin at 0, run gettingOlder twice, read the final age, and return messages. Each invocation sees the result of the preceding put, so the script reads imperatively while remaining a pure computation that can be run with an initial state and checked in a test. section 7.2.2
import Control.Monad.State (State, get, put) #1
type Age = Int
gettingOlder :: State Age String #2
gettingOlder = do
age <- get #3
let newAge = age + 1
put newAge #4
pure ("You're now older for 1 year: " <> show newAge)
This compact script shows the core pattern: read Age with get, calculate a replacement value, store it with put, and return a normal result. section 7.2.2
StateT extends the pattern when stateful logic must coexist with another monad. A Minesweeper board can live in StateT Board IO: revealCell reads and replaces the board with get and put, while lift adapts console IO into the surrounding stateful computation. section 7.2.3
When using StateT stacks, keep these operational distinctions clear:
- Lift an action when it belongs to an inner monad rather than the current StateT layer; deeper stacks require repeated lifting. section 7.2.3
- Treat each StateT layer as a distinct state context, such as score above board; a single combined state may be clearer and simpler when separate layers add no value. section 7.2.3
- Run a state layer with runState, execState, or evalState according to whether both result and state, only state, or only result are needed; use corresponding StateT runners for transformer layers. section 7.2.3
Stateful domain scripts and tests
A ship-control script can move SpaceshipModel from an explicit function argument into StateT SpaceshipModel LogicControl, leaving operations such as rotation to read and update contextual model state while lifting existing control actions. A board script can likewise be run from an initial board and assert the resulting board in a test. section 7.2.3 section 7.2.3
The pattern is for pure state that feels imperative but remains immutable. It complements rather than eliminates impure state: IORefs, MVars, vectors, and STM may be preferable when performance or convenience matters, provided mutable effects remain easy to control safely. section 7.3

