Monads & Functors
Composable containers that sequence computations while managing context (errors, async, nulls).
Overview
A monad is a design pattern from category theory that provides a way to chain operations on wrapped values while managing context, errors, nulls, asynchrony, state, without explicit conditional branching at every step. A functor is a simpler structure that wraps a value and allows a function to be mapped over it without changing the container.
Origin
Originating in category theory (Eilenberg and Mac Lane, 1940s), monads were applied to programming by Eugenio Moggi (1989) and made central to Haskell. The concept spread to mainstream languages through Promise (JavaScript), Option/Result (Rust, Scala), and Railway-Oriented Programming.
Examples
Promise as a monad (JavaScript)
// Promise chains: each .then() unwraps and re-wraps
fetchUser(id)
.then(user => fetchOrders(user.id)) // flatMap (bind)
.then(orders => orders.filter(o => o.active))
.then(orders => formatReport(orders))
.catch(err => handleError(err)) // error context propagated
// Without monads, every step needs explicit null/error checks:
const user = await fetchUser(id)
if (!user) return handleError('no user')
const orders = await fetchOrders(user.id)
if (!orders) return handleError('no orders')
// ... and so onResult monad for Railway-Oriented Programming in Ruby
# A simple Result monad (dry-monads is the production-grade library)
class Result
def self.ok(value) = new(:ok, value)
def self.err(msg) = new(:err, msg)
def initialize(type, value)
@type, @value = type, value
end
def bind
return self if @type == :err
yield @value
end
def map
return self if @type == :err
Result.ok(yield @value)
end
def unwrap = @value
end
# Usage: errors propagate without explicit branching
def process_order(id)
Result.ok(id)
.bind { |id| find_order(id) }
.bind { |order| validate_stock(order) }
.map { |order| calculate_total(order) }
.bind { |total| charge_payment(total) }
endIf any step returns an Err result, subsequent bind/map calls are skipped. The error propagates to the end without try/catch or explicit nil checks at each step.
Use Cases
- 01Chaining async operations (Promise, async/await)
- 02Propagating errors without try/catch at every call site (Result/Either monad)
- 03Handling optional values without nil checks everywhere (Option/Maybe monad)
- 04Composing validation steps where any failure short-circuits the chain
- 05Managing state transformations in reducers
When Not to Use
- //When the team has no functional programming background, the abstraction cost is high
- //For simple, one-step operations where the monad wrapper adds indirection for no gain
- //When error context needs to be handled immediately rather than propagated
Technical Notes
- The three monad laws (left identity, right identity, associativity) ensure that bind and wrap compose predictably, a library that breaks these is not a true monad
- dry-monads is the production-grade Ruby library for Result, Maybe, and Do notation
- JavaScript Promises do not fully satisfy the monad laws (Promise.resolve(Promise.resolve(x)) === Promise.resolve(x) in behaviour, but wrapping is automatic)
- Railway-Oriented Programming (Scott Wlaschin) is the most accessible introduction to monadic error handling for imperative programmers
More in Programming Techniques