Programming Techniques

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 on

Result 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) }
end

If 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