The Definitive Monad Explainer
(Community Consensus Version 14.7)
1. Overview edit
Monads. You've heard of them. You've feared them. You've spent three days reading tutorials and emerged more confused than before. This page represents the collective wisdom (and collective argument) of the CGPA community, distilled across 312 revisions into something approaching a coherent explanation.
A monad is a computational context. Like functors and applicatives, monads are represented with a typeclass in Haskell. Every monad is also an applicative functor, and every applicative is a functor โ these three form a hierarchy. The monad adds one crucial capability on top: the ability to chain operations that depend on previous results.
The key operations are return (wraps a value into a monadic context) and >>= (pronounced "bind"), which chains monadic computations together. Monads are also called programmable semicolons โ the behavior of >>= and >> varies per monad, giving you control over how sequencing works.
2. Mathematical Definition edit
This is my section and I will keep it rigorous. If you want hand-waving, scroll to ยง3. I will not be held responsible for any burritos. โ CurryCat
In Haskell, a monad is a typeclass defined by two core operations โ return (also called unit or pure) and (>>=) (bind):
class Monad m where -- Inject a plain value into the monadic context return :: a -> m a -- Chain: unwrap m a, pass value to (a -> m b), produce m b (>>=) :: m a -> (a -> m b) -> m b -- Sequence without passing the value (>>) :: m a -> m b -> m b m >> k = m >>= \_ -> k
2.1 The Monad Laws edit
Any valid monad instance must satisfy three laws. Note that Haskell's type system does not enforce these โ it is the programmer's responsibility to ensure compliance. [CurryCat: this is why some imposter monads sneak through โ looking at you, PurrIO's "CatState" implementation from 2022]
-- Left identity: return a >>= f โก f a leftIdentity :: (Eq (m b), Monad m) => a -> (a -> m b) -> Bool leftIdentity a f = (return a >>= f) == f a -- Checking with Maybe: -- return 5 >>= (\x -> Just (x * 2)) -- โก Just 5 >>= (\x -> Just (x * 2)) -- โก Just 10 โ -- Right identity: m >>= return โก m rightIdentity :: (Eq (m a), Monad m) => m a -> Bool rightIdentity m = (m >>= return) == m -- Just 42 >>= return โก Just 42 โ -- Nothing >>= return โก Nothing โ
2.2 Category Theory View edit
A monad, in the formal sense, is a monoid in the category of endofunctors. Formally, it is a triple (T, ฮท, ฮผ) where T is an endofunctor on a category C, ฮท: Id โ T is the unit natural transformation (return), and ฮผ: Tยฒ โ T is the multiplication (join). These satisfy coherence conditions corresponding exactly to the three monad laws above.
Note that join :: m (m a) -> m a (flatten nested monadic contexts) is equivalent to bind: m >>= f = join (fmap f m). You can define a monad with either (>>=) or join โ they're interchangeable. See Monad Transformers for why join becomes important later.
3. The Burrito Analogy ๐ locked
OK so imagine a burrito. A burrito is a tortilla wrapped around some filling. Now imagine your value is the filling, and the monadic context is the tortilla. return takes your filling and wraps it in a tortilla. (>>=) lets you open the burrito, do something with the filling, and get a new burrito back.
disputed A Maybe a is a burrito that either contains filling (Just a) or is an empty tortilla shell (Nothing). When you bind over a Nothing, the kitchen (runtime) says "no filling, no new burrito" and short-circuits the whole operation.
-- return wraps filling in a tortilla -- Just 3 is a burrito containing 3 -- Nothing is an empty tortilla shell addFilling :: Int -> Maybe Int addFilling n | n > 0 = Just (n * 2) -- made a burrito! ๐ฏ | otherwise = Nothing -- no filling ๐ฟ burritoChain :: Maybe Int burritoChain = Just 5 -- start with a burrito >>= addFilling -- open it, double the filling, rewrap: Just 10 >>= addFilling -- open it, double again: Just 20 >>= (\x -> Nothing) -- someone dropped the burrito ๐ฑ >>= addFilling -- no burrito to work with: Nothing -- Result: Nothing -- The short-circuit propagates automatically!
disputed by CurryCat Think of join as taking a burrito-inside-a-burrito (m (m a)) and merging the two tortilla layers into one โ though CurryCat will tell you this is "not how burritos physically work." CurryCat is technically correct. CurryCat is also missing the point.
m a somehow 'contains' a value of type a. This is not true for all monads โ there is no sense in which IO String contains a String." I've made my objections on record 14 times. They are part of the edit history. โ CurryCat
4. The Practical Approach edit
I wrote this section because after the 6th version of this page, it still had no actual working code examples. CurryCat's section is correct. FluffyBinder's section is fun. Neither of them will help you write a Haskell program. Here's what will. โ LambdaWhisker
Forget analogies. Forget category theory. Here's the deal: a monad is a design pattern for chaining operations where each step might do something extra โ like fail, perform I/O, carry state, or produce multiple results. You learn monads by using them. Start with Maybe.
4.1 The Maybe Monad โ Handling Failure edit
Maybe is the simplest monad. It represents computations that might fail. Without monads, you'd write nested case expressions everywhere. With the Maybe monad, failure propagates automatically:
-- WITHOUT monads: nested case hell ๐ฟ lookupUserCity :: UserId -> Maybe City lookupUserCity uid = case lookupUser uid of Nothing -> Nothing Just user -> case lookupAddress user of Nothing -> Nothing Just addr -> case lookupCity addr of Nothing -> Nothing Just city -> Just city -- WITH the Maybe monad: clean and flat ๐ธ lookupUserCityM :: UserId -> Maybe City lookupUserCityM uid = lookupUser uid >>= lookupAddress >>= lookupCity -- Or with do-notation (see ยง4.3): lookupUserCityDo :: UserId -> Maybe City lookupUserCityDo uid = do user <- lookupUser uid addr <- lookupAddress user lookupCity addr
The Maybe monad instance works as follows: if the left side of >>= is Nothing, the whole chain short-circuits to Nothing. If it's Just x, the value x is extracted and passed to the next function. Failure propagates automatically โ no manual checking.
instance Monad Maybe where return x = Just x Nothing >>= f = Nothing -- short-circuit! Just x >>= f = f x -- unwrap and continue
4.2 The IO Monad โ Real World Interaction edit
The IO monad is where Haskell interfaces with the real world โ reading files, printing to the terminal, making network requests. IO operations interact with systems outside your program. The IO monad sequences these effects in a controlled way while keeping the rest of your code pure. The type IO a describes an action that, when executed, will produce a value of type a.
-- The classic: your first IO action main :: IO () main = do putStrLn "nyaa~ what is your name? ๐พ" name <- getLine putStrLn ("hello, " ++ name ++ "! welcome to CGPA!") -- IO actions composed with >>= (desugared do-notation) main' :: IO () main' = putStrLn "nyaa~ what is your name? ๐พ" >> getLine >>= \name -> putStrLn ("hello, " ++ name ++ "!") -- Reading a file safely with error handling catFileContents :: FilePath -> IO () catFileContents path = do contents <- readFile path putStr contents
IO String does not "contain" a String. It's a description of an action that will produce one. This is CurryCat's main objection to the burrito analogy, and on this specific point, CurryCat is right. IO is not a box. IO is a recipe.
4.3 do Notation โ Syntactic Sugar edit
do notation is syntactic sugar that desugars to >>= and >> chains. It works for any monad, not just IO. The two rules are simple:
do { x <- e1; e2 } โก e1 >>= \x -> e2
Bind: extract x from e1, use it in e2
do { e1; e2 } โก e1 >> e2
Sequence: run e1, discard result, run e2
-- These are identical: -- do notation form: example1 :: Maybe String example1 = do x <- Just 3 y <- Just "!" return (show x ++ y) -- desugared bind form: example1' :: Maybe String example1' = Just 3 >>= (\x -> Just "!" >>= (\y -> return (show x ++ y))) -- Both produce: Just "3!" -- Failure case โ Nothing short-circuits everything: example2 :: Maybe String example2 = do x <- Just 3 _ <- Nothing -- ๐ฟ abort! y <- Just "!" -- never reached return (show x ++ y) -- Produces: Nothing
4.4 The List Monad โ Non-Determinism edit
The list monad models non-deterministic computation โ a computation that can return multiple results. Binding over a list applies the function to every element and concatenates the results.
-- List monad = non-determinism pairs :: [(Int, Int)] pairs = do x <- [1,2,3] -- x can be 1, 2, or 3 y <- [10,20] -- y can be 10 or 20 return (x, y) -- all combinations -- [(1,10),(1,20),(2,10),(2,20),(3,10),(3,20)] -- With filtering (guard): import Control.Monad (guard) pythagorean :: [(Int,Int,Int)] pythagorean = do a <- [1..20] b <- [a..20] c <- [b..20] guard (a*a + b*b == c*c) return (a, b, c) -- [(3,4,5),(5,12,13),(6,8,10),(8,15,17),(9,12,15)...]
5. Controversies edit
5.1 The Great Burrito War (2023โ2025) edit
The burrito analogy for monads originates from a satirical 2009 blog post that deliberately chose an absurd metaphor to illustrate the "monad tutorial fallacy" โ the mistake of assuming that a clever analogy will teach what only struggling through the details can teach. The analogy was widely understood as a joke.
In 2013, a separate post argued that monads are, in a technical sense, actually kinda like burritos โ and from that point, the meme took on a life of its own, with many users mistaking the joke for a genuine pedagogical tool.
CurryCat's objection: The burrito analogy implies that a monadic value contains an inner value and that it can always be unwrapped. This is false for many monads โ most notably IO. There is no sense in which IO String contains a String; it is an action that will produce one when executed. The analogy actively misleads.
FluffyBinder's defense: The analogy is accurate enough for the Maybe and List monads, which are where beginners spend most of their time. The IO edge case should be a footnote, not a reason to abolish the most intuitive intro to monads that exists. Real World Haskell agrees that beginners should learn by example.
LambdaWhisker's compromise position (v14.0 consensus): The burrito analogy can stay in ยง3 with prominent disclaimers. ยง4 provides a practical grounding that does not rely on the analogy. Both sections must acknowledge each other. This is the current state of v14.7.
2025-10-14: CurryCat marks ยง3 as "Disputed" and adds disclaimer
2025-10-14: FluffyBinder reverts, removes disclaimer
2025-10-14 through 2025-10-28: 47 reverts across 14 days
2025-10-29: PurrIO locks section, opens megathread
2025-11-02: v14.7 compromise text agreed upon by all parties
2025-11-02: Current state of page
5.2 Naming Disputes edit
On the name "return": Widely agreed to be a confusing name. In most programming languages, return exits a function. In Haskell, it merely wraps a value in a monadic context and does not exit anything. The choice of name is acknowledged as "a little unfortunate" even in Real World Haskell. pure (from Applicative) is considered a better name and is now preferred in modern Haskell.
On "monad" as a word: The word "monad" comes from ancient Greek, where it could mean "almost everything." It was adopted from category theory. It provides essentially no intuition from its etymology. Several CGPA members have proposed renaming it to "catchain" (FluffyBinder, 2021), "sequencebox" (MewTrans, 2022), and "computhingy" (anonymous, 2024). All proposals were rejected, mostly because Haskell already exists and we can't change it.
On "monadic" vs "monadal": MewTrans briefly edited ยง1 to use "monadal" consistently throughout. Reverted by 5 users within 12 minutes. MewTrans claims this was a joke.
6. Frequently Asked Questions edit
Monad that violates the laws โ it just won't behave as expected. This is why CurryCat keeps flagging PurrIO's CatState implementation.Applicative is a superclass of Monad, and Functor is a superclass of Applicative. The hierarchy is: Functor โ Applicative โ Monad. See the Functor Explainer and the Applicative Explainer.MaybeT IO a gives you IO actions that can also fail. This is where things get complicated. See the Monad Transformers page.7. See Also edit
- Functor Explainer โ prerequisite reading
- Applicative Explainer โ the step between functors and monads
- Monad Transformers โ combining monadic effects
- The State Monad โ managing mutable state in a pure context
- do Notation: A Complete Guide โ LambdaWhisker's full breakdown
- Haskell Type Classes โ how the Monad typeclass fits in
- Category Theory 4 Cats โ the rigorous mathematical background
- ๐พ Cat-Girl-Approved Learning Resources โ community-curated reading list
- The Burrito War Megathread โ extended community debate
- Language Wars: Haskell Thread โ where this article was first referenced
8. Edit History (Recent) full history โ
| Version | Date | User | Change | Summary |
|---|---|---|---|---|
| v14.7 | 2025-11-02 | PurrIO | +312 -289 | v14.7 consensus: compromise burrito text, add disclaimers, unlock in progress. Restore LambdaWhisker footnote in ยง4.2. |
| v14.6 | 2025-11-01 | CurryCat | -441 | REMOVED the burrito section entirely. See talk page. Not apologizing. |
| v14.5 | 2025-10-31 | FluffyBinder | +441 | Restored burrito section. It stays. Happy Halloween ๐ |
| v14.4 | 2025-10-30 | CurryCat | -441 | Removed burrito section (7th time). Added rigorous alternative. |
| v14.3 | 2025-10-28 | FluffyBinder | +441 -88 | Restored burrito section + expanded with new emoji. Removed CurryCat's snarky footnotes. |
| v13.1 | 2025-07-14 | MewTrans | +203 | Added ยง4.4 List Monad + Pythagorean triple example |
| v13.0 | 2025-07-01 | LambdaWhisker | +88 -12 | Clarified IO monad description: IO is a recipe, not a box. This is important and non-negotiable. |
| v12.1 | 2025-03-22 | CurryCat | +567 | Expanded ยง2.2 category theory view. Added ฮท and ฮผ notation. Community will learn to love it. |
| v9.0 | 2024-01-05 | All | +0 -0 | Community vote on v9 structure. 89% approval. First formal consensus. CurryCat's category theory section rejected 51-49. |
| v9.1 | 2024-01-06 | CurryCat | +412 | Added ยง2.2 category theory anyway under "Educational Override" clause. ClauseSubsequently removed by community vote. |
| v1.0 | 2019-03-11 | CurryCat | +891 | Initial page creation. "A monad is a monoid in the category of endofunctors. Read Maclane if you don't understand." |