Monads

Course 2 · Ch 3 — Central Chapter
Monads
The single biggest payoff in this entire 16-chapter track

Everything since haskell1-1 has been building toward this. haskell2-2 closed with an explicit promise: Applicative can't let a later computation depend on an earlier one's real value, and Monad is exactly what closes that gap. Here it is — and the payoff is bigger than just closing one gap.

The Gap Applicative Left Open

haskell2-2's own Challenge 3 asked for exactly this: looking up a user, then using that user's real email to look up their order. <*> structurally cannot do this — both its arguments must already be wrapped, independently, before it ever runs.

The Monad Typeclass & >>= (bind)

class Applicative m => Monad m where (>>=) :: m a -> (a -> m b) -> m b

Monad requires Applicative as its own superclass, extending haskell2-2's hierarchy further: Functor ← Applicative ← Monad. >>= ("bind") takes a wrapped value and a function that receives the real, unwrapped value, returning a new wrapped result. This is exactly the missing mechanism — the function passed to >>= genuinely sees the earlier computation's actual result.

Maybe as a Monad — Chaining Optional Computations

findUser 5 >>= \u -> findOrderByEmail (email u) -- if findUser 5 is Nothing, the whole chain short-circuits automatically -- if it's Just u, the lambda genuinely receives the real u and can use it

Directly resolving haskell2-2's own Challenge 3 example. If the lookup fails, >>= short-circuits to Nothing without ever calling the lambda; if it succeeds, the lambda receives the real, unwrapped User.

The Central Reveal — Maybe/Either/[]/IO Are All the Same Abstraction

This is the single most important paragraph in the entire track. Maybe's >>= short-circuits on Nothing. Either's >>= short-circuits on Left. []'s >>= represents nondeterminism — every combination of possible results, chained together. IO's >>= sequences side-effecting actions one after another. Four completely different-feeling use cases — optional values, error handling, multiple results, real-world side effects — are secretly implementations of the exact same single interface, differing only in what >>= actually does underneath. This is the "aha" this whole course has been building toward since haskell1-1's own main :: IO ().

do-Notation — Syntactic Sugar Over >>=

do u <- findUser 5 o <- findOrderByEmail (email u) return o -- desugars EXACTLY to: findUser 5 >>= \u -> findOrderByEmail (email u) >>= \o -> return o

do-notation isn't a separate control-flow feature — it's pure syntax sugar over >>=, no different in kind from haskell1-2's own currying being "secretly" a chain of one-argument functions. This retroactively explains every do block used without comment since haskell1-1's own main = do { ... }.

Closing the Loop — Rust's Option/Result Are Monad-Shaped

Back to the thread run through haskell1-6 and haskell1-8: Rust's own .and_then() method on Option<T>/Result<T, E>, and its ? operator, are directly equivalent to Haskell's >>=. Rust genuinely never uses the word "monad" anywhere in its own documentation or community culture — but the underlying structure and behavior is a monad in the formal sense. Stated plainly, as this course's own closing statement on the Rust lineage thread: the idea travelled, even where the name didn't.

The Monad Laws

A real, honest brief mention, same spirit as haskell2-1's own Functor laws: left identity, right identity, and associativity are expected of every lawful Monad instance — and, again, none of them are compiler-enforced. Pure convention, same as before.

TypeWhat >>= actually doesRust equivalent
Maybeshort-circuits on NothingOption::and_then / ?
Eithershort-circuits on LeftResult::and_then / ?
[]explores every combination of resultsno direct one-line equivalent
IOsequences side-effecting actionsordinary sequential statements
Reach for do-notation for readability in real code
It desugars to >>= automatically — no performance or capability difference, purely presentation. Nearly all real Haskell code uses do-notation for anything beyond the simplest one-step chain.
do-notation's sequential LOOK can obscure very different underlying behavior
A do block reads the same regardless of which monad it's written in — but a Maybe block might silently short-circuit, an [] block genuinely explores every combination rather than running once, and only IO's own version behaves like ordinary imperative sequencing. The sugar looks identical; the real behavior underneath genuinely isn't.

Coding Challenges

Challenge 1

Write findUser :: Int -> Maybe String and findOrderByEmail :: String -> Maybe String (stub implementations are fine), then chain them with >>= to resolve haskell2-2's own Challenge 3 scenario for real, testing both a successful lookup chain and one that fails partway through.

📄 View solution
Challenge 2

Rewrite Challenge 1's >>= chain using do-notation instead, and confirm both versions produce identical results for both the success and failure cases.

📄 View solution
Challenge 3

Write a short comment explaining, using a concrete example, how Rust's ? operator on a Result is behaviorally equivalent to Haskell's >>= on Either, even though Rust code never uses the word "monad" anywhere.

📄 View solution

Chapter 3 Quick Reference — The Track's Central Chapter

  • Monad requires Applicative (which requires Functor) — the full Functor ← Applicative ← Monad hierarchy
  • >>= (bind) passes the REAL unwrapped result of one computation to a function producing the next — closing Applicative's own dependency gap
  • THE central reveal: Maybe/Either/[]/IO all implement the same >>= interface, differing only in what bind actually does underneath
  • do-notation is pure sugar over >>= — no different in kind from currying being sugar over chained one-argument functions
  • Rust's Option/Result, .and_then(), and ? are genuinely monad-shaped, even though Rust never uses the word — closing the loop from haskell1-6/haskell1-8
  • The Monad laws (left/right identity, associativity) are pure convention, never compiler-enforced, same as haskell2-1's Functor laws
  • Next chapter: IO in depth — how IO stays a real, distinct type that "infects" every calling signature, paying off Chapter 1's own throughline in full