Monads: Sequencing with Context
The concept with the scariest reputation in all of Haskell, and the simplest honest description: a Monad lets one context-carrying step decide what happens next, based on what the previous step actually produced.
Where Applicative stopped
Chapter 10 ended on a real limitation: validateAge couldn’t see the result of validateName, because <*> combines two already-decided wrapped values — neither can react to the other. Monad closes exactly this gap with one operation:
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m b
>>= (“bind”) takes a wrapped value m a and a function that takes a plain a and produces a new wrapped value m b — crucially, that function is free to look at the actual a and decide what m b to produce next.
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)
safeSqrt :: Int -> Maybe Int
safeSqrt n
| n < 0 = Nothing
| otherwise = Just (round (sqrt (fromIntegral n)))
compute :: Int -> Maybe Int
compute n = safeDiv 100 n >>= safeSqrt
compute 4 first runs safeDiv 100 4, getting Just 25 — and because Monad lets the next step see that 25, it can feed it straight into safeSqrt, getting Just 5. compute 0 short-circuits to Nothing at the first step, and safeSqrt never even runs. This dependency — “what happens next depends on what just happened” — is precisely what Applicative structurally cannot express, and precisely what ordinary imperative sequencing (let a = ...; let b = f(a); ...) gives you for free in every other language. Monad is Haskell’s principled way of getting that same power back, without giving up purity.
Figure: The full picture. >>= chains one step into the next; the curved arrow, f >=> g (Kleisli composition), is what you get by fusing a whole chain of binds into a single reusable function — the monadic analogue of ordinary function composition ..
do notation is just >>= in disguise
Nested chains of >>= get visually noisy fast, so Haskell provides do notation as pure syntactic sugar over exactly the same thing:
compute' :: Int -> Maybe Int
compute' n = do
q <- safeDiv 100 n
safeSqrt q
-- desugars to EXACTLY:
compute'' n = safeDiv 100 n >>= \q -> safeSqrt q
This is the same do block from Chapter 7’s IO examples — getLine, readFile, and every other IO action are chained with the identical >>= operator, because IO is a Monad too. Once you’ve internalized Maybe’s version, IO’s do notation stops looking like special syntax and starts looking like the same idea, applied to a different context.
The laws, briefly
return a >>= f == f a -- left identity
m >>= return == m -- right identity
(m >>= f) >>= g == m >>= (\x -> f x >>= g) -- associativity
These three laws guarantee >>= behaves the way plain old function composition does — return/pure acts as a genuine identity, and chains of binds can be regrouped freely without changing meaning, exactly like . That’s what makes it safe to build large do blocks out of small ones without worrying about where the invisible parentheses fall.
“Monad” has an infamous reputation for being impossible to explain, largely because of a long history of reach-for-an-analogy blog posts (“a monad is like a burrito,” etc.) that focus on a metaphor instead of the actual, simple type signature. If anything in this chapter should stick, let it be the signature itself: m a -> (a -> m b) -> m b. Every correct intuition about monads is a specialization of that one line — there’s no deeper trick hiding behind it.
Different monads, different meanings — same shape
Just as with Applicative in Chapter 10, the type of >>= never changes, but its behaviour is entirely up to the instance:
-- Maybe: short-circuits on failure
Just 5 >>= (\x -> if x > 0 then Just (x*2) else Nothing) -- Just 10
-- []: explores every combination, keeping the sequencing
[1,2] >>= (\x -> [x, x*10]) -- [1,10,2,20]
-- IO: sequences real-world effects, in order
main = getLine >>= \name -> putStrLn ("Hi, " ++ name)
Maybe uses >>= to model “stop at the first failure.” Lists use it to model “branch into every possibility, and flatten the results back into one list.” IO uses it to enforce a strict order of effects in the real world. All three are legitimately, provably the same abstraction — which is exactly why a single do block syntax works, unmodified, across all of them.
Real-world usage
Monads aren’t a Haskell party trick — reaching for the right one is how idiomatic Haskell solves problems other languages solve with exceptions, null checks, and callback soup:
Maybereplaces null-pointer-style “might not have a value” bugs with a type the compiler forces you to handle — Chapter 3’ssafeHeadwas already aMaybe-returning function in disguise.Either e ageneralizesMaybeto carry an actual reason for failure (Left e) alongside success (Right a) — the standard way to model validation and parsing errors with useful messages, rather than a bareNothing.IO(Chapter 7) sequences file access, networking, and user input, while keeping the compiler’s guarantee that nothing outside anIO-tagged type can secretly touch the world.State s athreads a piece of mutable-looking state — a counter, a random seed, a game board — through a sequence of computations, without any of the underlying functions actually being impure; the “mutation” is really just each step passing the next state along explicitly, hidden behind>>=.[](the list monad) models non-deterministic search and combinatorial exploration — trying every legal chess move, every parse of an ambiguous grammar, every combination of ingredients that fits a budget.
Outside Haskell, the same shape shows up constantly under different names: JavaScript’s Promise.then, Rust’s Result::and_then and Option::and_then, C#‘s LINQ SelectMany, and Scala’s for-comprehensions are all, structurally, >>= wearing a language-specific costume. Learning Monad properly in Haskell — where the shape is explicit, named, and law-abiding — tends to make all of those other APIs click into place as special cases of one idea, rather than a pile of unrelated library methods to memorize individually.
The much-quoted, much-misunderstood one-liner — “a monad is just a monoid in the category of endofunctors” — is, once Chapters 9-11 are behind you, actually decodable: Monad is built on Applicative is built on Functor, i.e. on endofunctors of Hask (Chapter 9’s closing note); and >>=/return satisfy associativity and identity laws structurally identical to an ordinary monoid’s. It’s a genuinely accurate one-sentence definition — it’s just written for someone who’s already climbed the whole trail this book has been walking, which is exactly where you now stand.
This is the summit. From here, the honest next step isn’t another abstraction — it’s writing real Haskell, and letting Maybe, Either, IO, and eventually your own monads become as ordinary to you as if and for once were. See Further Reading for where to go next.