LambdaWhisker [not logged in]
CGPA:Wiki โ€บ Haskell โ€บ Concepts Stable Section Disputed ๐Ÿพ Cat-Approved

The Definitive Monad Explainer
(Community Consensus Version 14.7)

Collaboratively maintained by the CGPA community ยท Last stable consensus reached 2025-11-02 ยท 312 revisions ยท เธ…^โ€ข๏ปŒโ€ข^เธ…
โš  Community Notice: Section 3 (The Burrito Analogy) is currently under a cool-down lock following edit war #7 between FluffyBinder and CurryCat. Edits to that section require mod approval until 2025-12-01. See the megathread.
๐Ÿ“Œ Note: This is version 14.7 of the Definitive Monad Explainer. Previous versions are preserved in the edit history. Version 14.0 was the first to achieve a formal community vote (89% approval).
โ„น๏ธ For the category-theory derivation, see Category Theory 4 Cats. For monad transformers, see Monad Transformers. For related drama, see the Burrito War Megathread.

1. Overview edit

๐Ÿฑ CurryCat MathCat ยง Overview โ€” written v1.0, revised v8.3, v12.1

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.

๐ŸŒŸ Community Pinned Note โ€” mod: PurrIO
If you're completely new to Haskell, start with the Haskell Beginner Guide first. Monads will make much more sense after you understand functors and type classes. Alternatively, skip to ยง4 (LambdaWhisker's practical section) and come back here later.

2. Mathematical Definition edit

๐Ÿฑ CurryCat MathCat ยง Mathematical โ€” primary author, 118 edits to this section alone

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):

Haskell โ€” Typeclass Definition ๐Ÿ“ CurryCat
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 "Wrapping a value and immediately binding is the same as just applying the function."
Right Identity:   m >>= return  โ‰ก  m "Binding to return is a no-op."
Associativity:   (m >>= f) >>= g  โ‰ก  m >>= (ฮปx โ†’ f x >>= g) "Chaining is associative โ€” order of grouping doesn't matter."
Haskell โ€” Law Verification for Maybe ๐Ÿ“ CurryCat
-- 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

๐Ÿฑ CurryCat MathCat Added v9.1 โ€” against community protest
โš  Contested Section: The original community vote (v9.0) rejected including this subsection (51% against). CurryCat included it anyway under the "Educational Override" clause. The clause has since been removed from the wiki constitution.

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.

A monad is a triple (T, ฮท, ฮผ) satisfying: ฮผ โˆ˜ Tฮผ = ฮผ โˆ˜ ฮผT     (associativity) ฮผ โˆ˜ Tฮท = ฮผ โˆ˜ ฮทT = Id_T     (unit laws) Where T: Cโ†’C is an endofunctor, ฮท: 1_Cโ†’T (return), ฮผ: Tยฒโ†’T (join)
๐Ÿ’ฌ Community Note โ€” FluffyBinder
congrats you've now been maximally unhelpful to 98% of readers. the 2% who already knew this didn't need to read it. โ€” FluffyBinder [this note has 203 upvotes]
๐Ÿ’ฌ Response โ€” CurryCat
The 2% are the only ones this section was written for. The rest of you have ยง4. โ€” CurryCat [147 upvotes, 89 downvotes]

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

๐Ÿ” FluffyBinder AnalogyEnjoyer ยง Burrito โ€” original author, 94 revisions, 7 edit wars survived
๐Ÿ”’ Section Lock Active: This section is under a semi-lock following Edit War #7 (Octโ€“Nov 2025). Changes require approval from PurrIO (moderator). Current text is the v14.7 compromise agreed upon by all parties in the Burrito War Megathread.

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.

Haskell โ€” "The Burrito Version" (FluffyBinder) ๐ŸŒฏ FluffyBinder
-- 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!
๐Ÿพ FluffyBinder's Note: I know CurryCat hates this. I know the analogy breaks down for IO. I KNOW. But beginners understand it, and that's the point. You can graduate to the scary math later. The burrito stays. ๐ŸŒฏ

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.

๐Ÿ’ฌ Community Note โ€” CurryCat
"The burrito analogy strongly implies that a value of type 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

โš™๏ธ LambdaWhisker PragmatCat ยง Practical โ€” added v7.0, most-cited section per wiki analytics

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:

Haskell โ€” Maybe Monad: Before and After ๐Ÿ“ LambdaWhisker
-- 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.

Haskell โ€” Maybe Instance (for reference) ๐Ÿ“ LambdaWhisker
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

โš™๏ธ LambdaWhisker PragmatCat co-authored with PurrIO (v11.2)

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.

Haskell โ€” IO Monad Examples ๐Ÿ“ LambdaWhisker + PurrIO
-- 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
๐Ÿ’ก LambdaWhisker's Tip: Unlike Maybe, 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
Haskell โ€” do Notation Desugaring ๐Ÿ“ LambdaWhisker
-- 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

๐Ÿ“‹ MewTrans PragmatCat ยง List Monad โ€” added v13.0

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.

Haskell โ€” List Monad ๐Ÿ“ MewTrans
-- 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

โš–๏ธ PurrIO Moderator ยง Controversies โ€” written by mod to document the edit wars
โš”๏ธ ACTIVE CONTROVERSY โ€” The Burrito Analogy Debate

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.

โœ… 127 support FluffyBinder
โŒ 93 support CurryCat
๐Ÿ“œ Edit War #7 Timeline:
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

Why does Haskell need monads for I/O? Other languages don't.
Haskell is purely functional โ€” functions cannot have side effects in the type system. The IO monad provides a controlled way to sequence side-effectful operations while maintaining referential transparency everywhere else. The IO value is a description of an effect; the runtime actually executes it. See the Haskell guide for more on purity.
Does Haskell enforce the monad laws?
No. The Haskell type system cannot verify that monad instances satisfy the three laws (left identity, right identity, associativity). You must ensure this yourself when writing instances. A type can be made an instance of Monad that violates the laws โ€” it just won't behave as expected. This is why CurryCat keeps flagging PurrIO's CatState implementation.
Is "a monad is a monoid in the category of endofunctors" actually helpful?
Only if you already know what a monoid, a category, and an endofunctor are โ€” in which case you probably already understand monads. For everyone else, it is a shibboleth. It is technically true and pedagogically useless. Using it non-ironically in beginner contexts is considered poor form in most serious functional programming communities, including this one. (CurryCat disagrees. CurryCat is in the minority.)
Are all functors monads? Are all monads functors?
Not all functors are monads. All monads are functors (and applicatives). As of GHC 7.10, 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.
What are monad transformers?
Monad transformers let you combine multiple monadic effects. For example, MaybeT IO a gives you IO actions that can also fail. This is where things get complicated. See the Monad Transformers page.
I read the whole page and still don't understand monads. Help?
This is normal. The consensus of the community (and the broader Haskell literature) is that understanding monads requires actually writing code with them. Read ยง4, pick a monad (start with Maybe), write programs, break things, and eventually it will click. The "aha moment" typically arrives after 2โ€“4 weeks of use. Come ask questions in the main thread โ€” we are (mostly) friendly.

7. See Also edit


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."
๐Ÿพ Note: The full edit history (312 versions) is available at /wiki/monad-explainer-history.html. Versions 1.0 through 6.9 are archived and do not reflect current community consensus.