Pattern Matching

Pattern matching is the primary way to deconstruct data in IML. It is exhaustive — the compiler ensures you handle every possible case.

Basic matching

Use match ... with to match on values:

let describe_color = function
  | Red -> "red"
  | Green -> "green"
  | Blue -> "blue"
 
let area = function
  | Circle r -> Real.(3.14159 * r * r)
  | Rectangle (w, h) -> Real.(w * h)
  | Triangle (a, b, c) ->
    let s = Real.((a + b + c) / 2.0) in
    Real.sqrt Real.(s * (s - a) * (s - b) * (s - c))

The function keyword is shorthand for fun x -> match x with.

Matching on lists

Pattern matching on lists is especially common:

let rec sum = function
  | [] -> 0
  | x :: xs -> x + sum xs
 
let head_or_default default = function
  | [] -> default
  | x :: _ -> x
 
let rec zip xs ys =
  match xs, ys with
  | [], _ | _, [] -> []
  | x :: xs', y :: ys' -> (x, y) :: zip xs' ys'

Nested patterns

Patterns can be nested to match deeper structure:

let rec is_sorted = function
  | [] -> true
  | [_] -> true
  | x :: x' :: xs -> x <= x' && is_sorted (x' :: xs)
 
let rec flatten = function
  | Leaf a -> [a]
  | Node (Leaf a, Leaf b) -> [a; b]  (* Special case *)
  | Node (l, r) -> flatten l @ flatten r

Guards with when

Add conditions to patterns using when:

let rec num_occurs x = function
  | [] -> 0
  | hd :: tl when hd = x -> 1 + num_occurs x tl
  | _ :: tl -> num_occurs x tl
 
let classify_temp t =
  match t with
  | t when t < 0 -> "freezing"
  | t when t < 20 -> "cold"
  | t when t < 30 -> "comfortable"
  | _ -> "hot"

Wildcard and variable patterns

  • _ matches anything and discards the value
  • A variable name matches anything and binds the value
(* Ignore second element *)
let first (a, _, _) = a
 
(* Match any constructor *)
let is_leaf = function
  | Leaf _ -> true
  | _ -> false

Or-patterns

Match multiple patterns with the same body:

let is_weekend = function
  | "Saturday" | "Sunday" -> true
  | _ -> false
 
let is_binary = function
  | 0 | 1 -> true
  | _ -> false

Exhaustiveness

IML requires pattern matching to be exhaustive: every possible value must be handled. The compiler will warn (or error) if you miss a case:

(* This is complete — all constructors handled *)
let show = function
  | Red -> "red"
  | Green -> "green"
  | Blue -> "blue"
 
(* Missing Blue would be an error *)

Exhaustiveness is essential for formal reasoning — it guarantees your functions are total (defined on all inputs).

Matching on records

Destructure records in patterns:

let distance { x; y } =
  Real.sqrt Real.(x * x + y * y)
 
let is_origin { x; y } =
  Real.(x = 0.0 && y = 0.0)

Matching on tuples

let max_pair = function
  | (a, b) when a >= b -> a
  | (_, b) -> b
 
let both_positive (x, y) = x > 0 && y > 0