Dive C1.05.2
Guards & destructuring
Add a test to an arm; pull a tuple apart on the way in.
A plain pattern recognises a shape, but sometimes the shape is not enough — two negative numbers
and two positives share the structure of an int. A when
guard attaches a boolean test to an arm: the arm fires only if the pattern matches and the
guard is true. Or-patterns let several patterns share one result. And a pattern is not confined
to match — it works in a let binding and in a function
parameter, so let (x, y) = point binds both coordinates at once.
The when guard
The function describe sorts an int into three buckets. The
first arm is a constant pattern for zero. The second is a variable pattern x with a
guard when x < 0 — it matches any integer, but fires only when the guard holds. The
last arm catches everything that remains. Because zero is handled first, the guarded arm and the wildcard
partition the non-zero values into negative and positive.
let describe n = match n with | 0 -> "zero" | x when x < 0 -> "negative" // guard: matches, then tests x < 0 | _ -> "positive" describe -3 // val it : string = "negative" describe 0 // val it : string = "zero" describe 5 // val it : string = "positive"
- when
- a boolean test on an arm; the arm fires only when the pattern matches and the guard is true.
- order
- arms are tried top to bottom; the first whose pattern matches and whose guard holds wins.
- fall-through
- if a guard fails, matching continues at the next arm — the guarded arm does not consume the value.
when x < 0 does not count toward exhaustiveness, so a match
built only from guarded arms may still warn FS0025 — a final unguarded arm
(| _) closes the cover.Or-patterns and destructuring
When several cases share a result, an or-pattern joins them with |
inside a single arm. Each alternative must bind the same names (here none), so the arm's result can use them
uniformly.
let isVowel c = match c with | 'a' | 'e' | 'i' | 'o' | 'u' -> true // or-pattern: any of the five | _ -> false isVowel 'e' // val it : bool = true isVowel 'z' // val it : bool = false
A pattern also stands on the left of a let and in a function's parameter list. There
it is irrefutable — it must always match — which suits a tuple or a record whose shape is fixed. The binding
let (x, y) = point names both coordinates; dist destructures
its argument directly in the parameter position.
let point = (3.0, 4.0) let (x, y) = point // x = 3.0, y = 4.0 let dist (x, y) = sqrt (x*x + y*y) // destructure in the parameter dist point // val it : float = 5.0
A guard refines a structural match with a test; an or-pattern merges cases; a destructuring
let pulls a fixed shape apart. The pattern is the same tool in every position.
Evaluate describe n
Pick an n below. The evaluator walks the three arms in order — is it
0? then when x < 0? else the wildcard — and lights the
first that takes the value. The static table holds the same three answers, so the result stands without the
script.
describe n · three arms, tried in order
| n | arm that fires | why | result |
|---|---|---|---|
| -3 | | x when x < 0 | not 0; matches x, guard -3 < 0 holds | "negative" |
| 0 | | 0 | the constant pattern matches first | "zero" |
| 5 | | _ | not 0; guard 5 < 0 fails; wildcard takes it | "positive" |
Zero is caught by structure, the negatives by a guard, the rest by the wildcard. The order is the logic — move the wildcard up and the other arms never run.
Guard
A boolean test layered on a pattern, evaluated only after the pattern itself matches.
F# today
| x when x < 0 binds x, then fires only if the test on it holds.
Where this leads
Guards and or-patterns refine the cases F# supplies. The final dive goes further: an
active pattern lets a programmer define a new case by name — a function that the matcher calls
as if it were built in — so | Even and | Int v read like
| 0 and | x :: rest do here.
References
Sources
- Microsoft — F# documentation — pattern matching with
whenguards, or-patterns, andletdestructuring. - The F# Language Specification — guarded rules, the precise grammar of or-patterns, and patterns in bindings.
- The F# Software Foundation — the language's home and its learning resources.
Related in this course
- C1.05.3 — Active patterns — naming a case of your own.
- C1.05 — Pattern matching — the module hub and its three dives.
- Elixir — Functional core — guards on a clause head, the BEAM's analog.