A monad stack combines nested monads so one computation can use several effects through a single monadic interface. Monad transformers make the composition: for example, StateT Board IO carries mutable board state while retaining IO actions. The result is itself a monad, so ordinary do-notation still organizes the workflow. section 2.1.2 section 7.2.3
The practical cost is that each layer owns its own operations. Code at the top layer acts directly on its context; reaching an underlying layer requires lift, and reaching IO beneath two state layers requires lift twice. This makes effect boundaries explicit, but long stacks can become awkward enough that a single combined state is often clearer. section 7.2.3
Minesweeper state and console output
Define BoardMonad a = StateT Board IO a. A reveal-cell action gets and updates the Board directly, while lift (putStrLn ...) logs attempted moves and outcomes. Adding StateT Score above BoardMonad separates score from board state: get reads score, lift get reads the board, and lift (lift ...) performs console IO. Tests run each StateT layer in turn, supplying initial score and board values. section 7.2.3
Stacks also mark application boundaries. A Servant handler can be ReaderT Runtime (ExceptT ServerError IO): ReaderT supplies runtime needed to run a business scenario, ExceptT handles server errors, and IO performs impure work. Multiple lifts cross these layers, while domain exceptions can be converted to HTTP errors at the handler boundary. section 14.2.2 section 14.3
Figure 14.8 Monad stack for Servant handlers
Inspect the handler stack to see Runtime access, server-error handling, and IO arranged as distinct layers. section 14.2.2
