Programming
Functional Programming
Computer Science
Haskell
Monads

What is a monad?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A monad is a design pattern in functional programming that provides a structured way to chain operations together while handling side effects (I/O, state, errors, optionality). In practical terms, a monad is a type that implements two operations: wrapping a value (return/unit) and chaining computations (bind/>>=/flatMap). If you have used Promise.then() in JavaScript, Optional.flatMap() in Java, or list comprehensions in Python, you have already used monads.

The Three Monad Laws

A type is a monad if it provides:

  1. return (or unit): Wraps a plain value in the monadic context
  2. bind (or >>= or flatMap): Takes a monadic value and a function that returns a monadic value, chains them together

And satisfies three laws:

haskell
-- 1. Left identity: return a >>= f  ≡  f a
-- 2. Right identity: m >>= return    ≡  m
-- 3. Associativity:  (m >>= f) >>= g ≡  m >>= (\x -> f x >>= g)

In simpler terms: wrapping and then chaining is the same as just calling the function directly, chaining with the wrapper does nothing, and the order of grouping chains does not matter.

The Maybe/Optional Monad

The most intuitive monad handles values that might be absent:

haskell
1-- Haskell
2safeDivide :: Float -> Float -> Maybe Float
3safeDivide _ 0 = Nothing
4safeDivide x y = Just (x / y)
5
6-- Chaining operations that might fail:
7result = Just 10 >>= (\x -> safeDivide x 2) >>= (\y -> safeDivide y 0)
8-- result = Nothing (division by zero propagates automatically)

In Java, this is Optional:

java
1Optional<Double> result = Optional.of(10.0)
2    .flatMap(x -> safeDivide(x, 2.0))    // Just 5.0
3    .flatMap(x -> safeDivide(x, 0.0));   // Empty — short-circuits
4// result = Optional.empty()

In Python (with a simple implementation):

python
1def safe_divide(x, y):
2    return None if y == 0 else x / y
3
4def bind(value, func):
5    if value is None:
6        return None
7    return func(value)
8
9result = bind(bind(10, lambda x: safe_divide(x, 2)),
10              lambda y: safe_divide(y, 0))
11# result = None

The List Monad

Lists are monads where bind maps a function over each element and flattens the results:

haskell
-- Haskell
[1, 2, 3] >>= (\x -> [x, x*10])
-- Result: [1, 10, 2, 20, 3, 30]
python
# Python equivalent using list comprehension (which IS the list monad)
result = [y for x in [1, 2, 3] for y in [x, x*10]]
# [1, 10, 2, 20, 3, 30]
javascript
// JavaScript
[1, 2, 3].flatMap(x => [x, x * 10])
// [1, 10, 2, 20, 3, 30]

flatMap is literally bind for arrays — map then flatten.

The Promise/Async Monad

Promises in JavaScript are monads:

javascript
1// return = Promise.resolve (wraps a value)
2// bind   = .then (chains computations)
3
4Promise.resolve(5)                          // return
5    .then(x => fetchUser(x))                // bind — returns a new Promise
6    .then(user => fetchOrders(user.id))     // bind — chains another async op
7    .then(orders => console.log(orders));

Each .then() takes a value, applies a function that returns a Promise, and chains them — exactly the monad pattern.

The IO Monad (Haskell)

Haskell uses the IO monad to handle side effects in a pure functional language:

haskell
1main :: IO ()
2main = do
3    putStrLn "What is your name?"   -- IO action
4    name <- getLine                  -- bind: extract value from IO
5    putStrLn ("Hello, " ++ name)    -- another IO action

The do notation is syntactic sugar for monadic bind. Without it:

haskell
main = putStrLn "What is your name?" >>= (\_ ->
       getLine >>= (\name ->
       putStrLn ("Hello, " ++ name)))

The Result/Either Monad

Handles computations that can fail with an error:

rust
1// Rust: Result<T, E> is a monad
2fn parse_and_double(s: &str) -> Result<i32, String> {
3    s.parse::<i32>()
4        .map_err(|e| format!("Parse error: {}", e))
5        .and_then(|n| {
6            if n > 1000 {
7                Err("Number too large".to_string())
8            } else {
9                Ok(n * 2)
10            }
11        })
12}
haskell
1-- Haskell: Either
2safeDivide :: Int -> Int -> Either String Int
3safeDivide _ 0 = Left "Division by zero"
4safeDivide x y = Right (x `div` y)
5
6result = Right 100 >>= (\x -> safeDivide x 5) >>= (\y -> safeDivide y 0)
7-- Left "Division by zero"

Why Monads Matter

Without monads, chaining operations that might fail requires nested conditionals:

python
1# Without monads — nested error checking
2user = get_user(id)
3if user is not None:
4    address = get_address(user)
5    if address is not None:
6        city = get_city(address)
7        if city is not None:
8            return city
9return None
10
11# With monadic chaining (flatMap)
12result = (get_user(id)
13    .flat_map(get_address)
14    .flat_map(get_city))

Monads eliminate the pyramid of doom by providing a uniform interface for chaining.

Monads in Common Languages

LanguageMonad Typereturnbind
HaskellMaybe aJust>>=
JavaScriptPromise<T>Promise.resolve.then
JavaOptional<T>Optional.of.flatMap
RustResult<T, E>Ok(v).and_then
ScalaOption[T]Some(v).flatMap
SwiftOptional<T>.some(v).flatMap
C#Task<T>Task.FromResultawait / ContinueWith

Common Pitfalls

  • Overcomplicating the concept: A monad is just a type with flatMap (or bind) and return that follows three laws. You do not need category theory to use monads — you already use them via Promises, Optionals, and lists.
  • Confusing map with flatMap: map transforms the value inside a monad. flatMap transforms and then flattens (unwraps one layer). Using map where you need flatMap gives you nested monads (Optional<Optional<T>>).
  • Monad tutorials: The running joke is that anyone who understands monads immediately loses the ability to explain them. Start with practical examples (Maybe, Promise) rather than category theory.
  • Monads are not about side effects: While the IO monad handles side effects, monads in general are about chaining computations. Lists, optionals, and results are monads with no side effects.
  • Breaking the laws: Custom monad implementations that violate the three laws cause unexpected behavior when composed. Always verify left identity, right identity, and associativity.

Summary

  • A monad is a type with return (wrap a value) and bind/flatMap (chain computations) that follows three laws
  • Common monads: Maybe/Optional (handles absence), List (handles multiple values), Promise (handles async), Either/Result (handles errors)
  • You already use monads: Promise.then(), Optional.flatMap(), list comprehensions, Result.and_then()
  • Monads eliminate nested conditionals by providing a uniform chaining interface
  • The key insight: monads let you compose functions that return "wrapped" values without manually unwrapping at each step

Course illustration
Course illustration

All Rights Reserved.