The Option Type

Handling Missing Values

i
-- Option α is either (some value) or none; there is no null in Lean
def safeDivide (a b : Int) : Option Int :=
  if b == 0 then none else some (a / b)

#guard safeDivide 10 2 == some 5
#guard safeDivide 10 0 == none

-- map transforms the value if present; none passes through unchanged
#guard (safeDivide 10 2).map (· * 3) == some 15
#guard (safeDivide 10 0).map (· * 3) == none

-- getD supplies a default when the value is absent
#guard (safeDivide 10 0).getD (-1) == -1

-- Explicit pattern match when the two branches need different logic
def describe (a b : Int) : String :=
  match safeDivide a b with
  | some q => s!"{a} / {b} = {q}"
  | none   => "cannot divide by zero"

#eval describe 10 3    -- "10 / 3 = 3"
#eval describe 10 0    -- "cannot divide by zero"

-- do notation: each <- short-circuits the chain if the result is none
def compute (a b c : Int) : Option Int := do
  let x  safeDivide a b
  let y  safeDivide x c
  return x + y

#guard compute 10 2 1 == some 10   -- x=5, y=5, x+y=10
#guard compute 10 0 1 == none      -- first step fails; chain stops

Lists and Option

i
-- find? returns Some of the first element matching the predicate
def lookup (key : String) (pairs : List (String × Int)) : Option Int :=
  (pairs.find? (·.1 == key)).map (·.2)

#guard lookup "b" [("a", 1), ("b", 2), ("c", 3)] == some 2
#guard lookup "z" [("a", 1), ("b", 2)] == none

-- filterMap applies a function and keeps only the Some results
def onlyEvens (xs : List Nat) : List Nat :=
  xs.filterMap fun n => if n % 2 == 0 then some n else none

#guard onlyEvens [1, 2, 3, 4, 5] == [2, 4]

-- findSome? returns the first Some produced by the function
def firstOver (limit : Int) (xs : List Int) : Option Int :=
  xs.findSome? fun x => if x > limit then some x else none

#guard firstOver 3 [1, 2, 4, 5] == some 4
#guard firstOver 10 [1, 2, 4, 5] == none

-- Combine find? with map for safe lookups in association lists
def scores : List (String × Nat) := [("Alice", 95), ("Bob", 80)]

def grade (name : String) : String :=
  match (scores.find? (·.1 == name)).map (·.2) with
  | some s => if s >= 90 then "A" else "B"
  | none   => "unknown"

#eval grade "Alice"    -- "A"
#eval grade "Bob"      -- "B"
#eval grade "Carol"    -- "unknown"

Exercises

Safe Head

Chain of Divisions

Lookup with Default

Parse Rows

All Present