Module C3.03
Pattern matching, sharpened
Matching is the habit that crosses unchanged — then the compiler starts checking it.
Pattern matching is the most familiar bridge from Elixir. You already match
{:ok, v} against {:error, _}, destructure tuples and maps,
and guard with when. F# does all of it the same way — case … do …
end becomes match … with, the ;-clauses become
|-arms, and the shapes are identical. What F# adds is two powers Elixir does not have
at compile time. The compiler checks that a match covers every case and reports the missing
one before the program runs (the FS0025 warning); and active patterns
let you name a match case with your own logic and use it like a built-in. This module takes the three in turn.
The same construct, then two new powers
An Elixir developer reads a match arm on sight: it is the
case they write every day. So the first dive is a tour of familiar ground — the
tagged tuple, the list head/tail, the record, the when guard, all of it — in F#
syntax. Nothing to unlearn.
The next two dives are where F# does more. Exhaustiveness: in Elixir a
case that omits a clause compiles and raises CaseClauseError
only when an unhandled value arrives at run time; in F# the compiler reads the union and reports the missing
case at compile time. Active patterns: a small function in banana brackets
(| … |) that the matcher treats as a pattern — the F# answer to a chain of Elixir
cond clauses or inline guards, named once and matched like a built-in.
The three dives · select one
The tagged tuple, the list head/tail, the record, the when guard — everything you match on in Elixir, in F# syntax. case do … end becomes match … with.
Open this dive · or pick another above
The match arm is the one piece of F# an Elixir developer reads with no translation. The compiler's checking of it is the part that is new.
The same match, in one block each
The bridge is concrete. Where Elixir writes a case over a tagged tuple, F# writes a
match over a Result. The clause order, the wildcard
_, the bind on the matched value — all the same. The F# echo below was produced by
dotnet fsi.
Elixir — case over a tagged tuple
# Elixir case result do {:ok, v} -> v {:error, _} -> 0 end
F# — match over a Result
let describe result = match result with | Ok v -> v | Error _ -> 0 // describe (Ok 42) -> 42 // describe (Error "boom") -> 0
Elixir habit
case … do … end with {:ok, v} / {:error, _} clauses — checked when a value arrives at run time.
F# form
match … with over Ok v / Error _ — the same shape, checked for completeness at compile time, extensible with active patterns.
The three dives
Read them in order. Each stands on valid F# beside the real Elixir it parallels: every
// echo below was produced by dotnet fsi, and the FS0025
warning text in the second dive is the line the compiler actually emits. Together they take an Elixir
developer from the case they know to the two things F# adds on top of it.
Familiar ground
The tagged tuple, list head/tail, records and when guards — the same shapes, the F# syntax.
- the casematch you already know, side by side with Elixir
Exhaustiveness
Drop a case: F# warns FS0025 at the keyboard; Elixir raises CaseClauseError at run time.
- the checkthe compiler checks every case and names the missing one
Active patterns
Name a case with your own logic — complete (|Even|Odd|) or partial (|Int|_|) — and match on it.
- your casebanana brackets — the F# answer to a chain of
condguards
References
Sources
- Microsoft — F# documentation — pattern matching, match expressions, and active patterns.
- FSharp.Core API reference — the
ResultandOptiontypes matched against here. - Microsoft — .NET documentation — the runtime and type system the compiler checks against.
Related in this course
- C3.03.1 — Familiar ground — the Elixir match habits, in F#.
- C1.05 — Pattern matching & active patterns — the same material in language depth.
- Elixir — The match operator —
=as assertion,case, and destructuring on the BEAM.