100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Monads Explained

Learn how the Monad type class extends Applicative with >>= (bind), letting a computation's result decide what runs next, and how do-notation, Maybe, list, and Either all build on it.

Functional AbstractionsIntermediate10 min readJul 10, 2026
Analogies

Why Applicative Isn't Enough: Sequencing Dependent Computations

Applicative combines independent effectful values, but many real computations are dependent: the second step needs to inspect the result of the first step before it can decide what to run next, and the shape of that second computation may vary based on that value. Monad captures this pattern of 'run an effectful computation, then feed its result into a function that produces the next effectful computation,' which Applicative's <*> -- being fixed to a single wrapped function -- cannot express.

🏏

Cricket analogy: Deciding whether to review an umpire's decision depends entirely on the outcome of the previous ball -- you can't decide to call for DRS until you know if the batter was given out, which is exactly the dependent sequencing Monad captures that Applicative can't.

The Monad Type Class: return and >>=

The class is class Applicative m => Monad m where (>>=) :: m a -> (a -> m b) -> m b, pronounced 'bind.' >>= takes a value in a monadic context, a function from a plain value to a new monadic context, runs the first computation, and feeds its unwrapped result into the function to get the next computation. Haskell's do-notation is syntactic sugar for chains of >>=: do { x <- m1; y <- f x; return y } desugars to m1 >>= \x -> f x >>= \y -> return y.

🏏

Cricket analogy: do { runs <- scoreBall; wickets <- checkWicket runs; return (runs, wickets) } is like a scorer who first records the runs off a ball, then, using that specific outcome, decides whether to log a wicket -- each step depends on the previous one's actual result, not just its type.

haskell
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)

calc :: Int -> Int -> Int -> Maybe Int
calc a b c = do
  step1 <- safeDiv a b
  step2 <- safeDiv step1 c
  return (step2 + 1)

main :: IO ()
main = do
  print (calc 100 5 2)  -- Just 11
  print (calc 100 0 2)  -- Nothing

do-notation is purely sugar over >>= and return/pure -- there is no special 'do magic'; the compiler mechanically rewrites every x <- action line into action >>= \x -> ... before type-checking, which is why do-blocks work identically for Maybe, lists, IO, or any other Monad instance.

Monad Instances: Maybe, List, and Either

For Maybe, >>= short-circuits on Nothing exactly like calc above, propagating failure automatically once any step fails. For lists, >>= maps a function producing a list over every element and concatenates the results -- [1,2,3] >>= \x -> [x, x*10] yields [1,10,2,20,3,30] -- which is how list comprehensions and nondeterministic search are expressed in do-notation. For Either e, >>= behaves like Maybe but threads a specific error value of type e through the Left case instead of a generic absence, letting calc-style pipelines return a meaningful error message on failure.

🏏

Cricket analogy: Maybe's bind short-circuiting on a failed run-chase calculation is like a required-run-rate calculator instantly giving up the moment overs remaining hits zero, propagating the 'match over' failure through every subsequent step without further computation.

Monad Laws

Every Monad instance must satisfy three laws: left identity, return a >>= f == f a; right identity, m >>= return == m; and associativity, (m >>= f) >>= g == m >>= (\x -> f x >>= g). These laws guarantee that chaining binds behaves consistently regardless of how the chain is grouped or how many trivial returns are inserted, which is what makes refactoring a do-block -- splitting one step into two, or inlining a helper -- safe.

🏏

Cricket analogy: The monad law m >>= return == m is like confirming that recording a boundary and then simply re-confirming it happened, with no extra edit, leaves the scoreboard exactly as it was before the redundant re-confirmation.

A common beginner mistake is trying to escape IO by imagining some 'unsafePerformMonad' to get a plain value out of IO a inside pure code -- but Monad doesn't unwrap values outside their context; >>= only ever lets you use the value inside another computation of the *same* monad. Once a value is in IO, ordinary pure code can't see it directly, which is by design, not a limitation to work around.

  • Monad extends Applicative with >>= (bind), which sequences a computation into a function producing the next computation.
  • >>= lets a later step depend on the result of an earlier step, unlike Applicative's <*>.
  • do-notation is sugar for chains of >>= and return/pure, nothing more.
  • Maybe's >>= short-circuits on Nothing; list's >>= implements nondeterministic mapping and concatenation; Either's >>= threads a typed error.
  • The three monad laws (left identity, right identity, associativity) guarantee predictable, refactor-safe bind chains.
  • return/pure lifts a plain value into the monad with no extra effect.
  • A monadic value's content can't be pulled out into pure code -- only used inside another computation of the same monad.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#HaskellStudyNotes#MonadsExplained#Monads#Explained#Applicative#Isn#StudyNotes#SkillVeris#ExamPrep