DEV Community

Thierry Pfister
Thierry Pfister

Posted on

The Maybe Monad in F#

The Problem

Consider fetching a user, then their profile, then their avatar URL. In imperative code you write three null checks. Miss one and you ship a bug. The checks are noise — they obscure what the code actually does.

The Core Idea

The Maybe monad wraps a value that might or might not exist. F# ships with this built-in: it's called Option<'T>. A value is either Some x or None. The monad pattern gives you a way to chain operations on that wrapped value without ever manually unwrapping and re-checking.

The two operations that make it a monad:

  • return — wrap a plain value: Some 42
  • bind (>>=) — apply a function that itself returns an Option, and flatten the result
let bind (opt: 'a option) (f: 'a -> 'b option) : 'b option =
    match opt with
    | None   -> None
    | Some x -> f x
Enter fullscreen mode Exit fullscreen mode

If the input is None, the chain short-circuits immediately. If it's Some, the function runs and its result (which is itself an Option) becomes the next value in the chain.

A Runnable Example

Below is a self-contained pipeline: look up a user, get their config, read a specific key — any step can fail, and the types enforce that you handle it.

type User   = { Name: string; ConfigId: int }
type Config = { Id: int; Settings: Map<string, string> }

let users   = Map.ofList [ (1, { Name = "Alice"; ConfigId = 42 }) ]
let configs = Map.ofList [ (42, { Id = 42; Settings = Map.ofList [ ("theme", "dark") ] }) ]

let findUser   id     = Map.tryFind id users
let findConfig id     = Map.tryFind id configs
let getSetting k cfg  = Map.tryFind k cfg.Settings

// Chain with Option.bind — no manual null checks
let theme =
    findUser 1
    |> Option.bind (fun u  -> findConfig u.ConfigId)
    |> Option.bind (fun cfg -> getSetting "theme" cfg)

printfn "%A" theme   // Some "dark"

let missing =
    findUser 99
    |> Option.bind (fun u  -> findConfig u.ConfigId)
    |> Option.bind (fun cfg -> getSetting "theme" cfg)

printfn "%A" missing  // None
Enter fullscreen mode Exit fullscreen mode

Why This Is a Monad

Three laws must hold (and they do for Option):

  1. Left identitySome x |> Option.bind f equals f x
  2. Right identityopt |> Option.bind Some equals opt
  3. Associativity — chaining two binds in sequence equals nesting them

You don't need to memorize these to use Option correctly, but knowing they hold means you can trust the composition — no surprises when you chain 10 steps deep.

F# Computation Expressions

F# lets you write the same pipeline in maybe { } style using computation expressions — syntactic sugar over bind. That's a natural next step once the raw mechanics are clear.

Key Takeaway

Option.bind is your null-check replacement. Every time you find yourself writing if result <> null then ..., ask whether an Option chain expresses the intent more clearly. It usually does.

Top comments (0)