Pattern matching with match
One form for choosing between shapes: literal patterns, a binding pattern, guards written with when, and the wildcard that catches the rest.
Patterns, in order
match is a list of patterns and answers. The first pattern that fits is chosen and its arrow becomes the value of the whole match, so order decides meaning: a broad pattern placed above a narrow one makes the narrow one unreachable, and the compiler points that out rather than staying quiet. The warning is Warning 11: this match case is unused, and it is one of the few warnings worth treating as an error.
A pattern describes a shape rather than naming a value. 0 matches zero, _ matches anything without giving it a name, and a lowercase name matches anything and binds it for the answer on the right of the arrow. Because match is an expression, it can stand wherever a value can, including inside a printer.
Guards, and alternatives
A guard adds a condition to a pattern: | n when n < 0 -> ... fits only when the pattern matched and the condition is true. A vertical bar inside a pattern lists alternatives, so | 12 | 1 | 2 -> "winter" handles three months in one line.
The compiler checks that the patterns between them cover every value the type allows. For an int that is impossible to do exhaustively, and the wildcard is how you say that the rest are handled; for a variant with three constructors, leaving one out earns a warning that names it.
Patterns describe shapes, not values
A pattern is a little picture of a value. 0 is a picture of zero, _ is a picture of anything, and x is a picture of anything that also gives the thing a name. When a pattern contains a name, matching succeeds and the name is bound for the answer on the right; when it contains a literal, the value has to be equal for the branch to run.
That distinction is why lowercase names are not constants. match n with x -> ... matches every number and calls it x; it does not compare n with an existing x. A constructor such as Some or Circle is different: it starts with a capital and is a real pattern.
The compiler walks the patterns from the top and checks coverage. For a variant it knows every constructor, so it can warn when one is missing. For an int it cannot list the values, so a wildcard is how you say the rest are handled — but a wildcard placed above a specific case makes that case unreachable, and the compiler will tell you that too.
How a match is evaluated
Evaluation is a linear scan. The compiler turns the patterns into a series of tests, and the first test that succeeds takes its branch; no later pattern is considered, even if it looks more specific. That is the operational meaning of "first that fits", and it is why the order in the source is the order in the program.
A pattern can also fail partway. | Some (x, y) -> ... first checks that the value is a Some, then that its payload is a pair; if either test fails, the match moves to the next pattern with nothing bound. The compiler decides those tests when it builds the file, so the run-time cost is a few comparisons, not a search.
What goes wrong
The unreachable branch is the classic. | _ -> ... above | 0 -> ... means the zero case can never run, and the compiler says so with a warning rather than an error. Treat that warning as a bug: the branch you wrote is dead code, and the fix is almost always to move the wildcard down.
The other is a missing case in a variant. Add a constructor to a type and every match that does not handle it becomes a warning, then a run-time exception if the value ever arrives. The warning names the constructor and the file; follow it to the match and add the branch.
Three traps: a wildcard above a specific pattern hides it, so the specific branch never runs; a lowercase name in a pattern binds instead of comparing, so it matches everything; and a match used as an argument needs brackets around it — print_endline (match n with ...) — because the branches would otherwise swallow the rest of the line.
Try these next
- Write
day_kindthat groups weekdays and weekends with alternatives, and answers a different word for each. - Add a guard to a number match so zero is handled before negative numbers, then test both.
- Put a wildcard at the top, read the compiler warning, then move it to the bottom.
Alternatives in one pattern
let season month =
match month with
| 12 | 1 | 2 -> "winter"
| 3 | 4 | 5 -> "spring"
| _ -> "the rest"
let () = print_endline (season 1)Three labels and a half
Run it and read the six lines. Then add a pattern for 100 that prints `a hundred`, and move the wildcard to the top to see what the compiler says about the branches below it.
Words, alternatives and a guard
Run it and read the lines. Then add `"maybe"` as a third answer, and give the number classifier a branch for exactly 10.
Read a match upwards when it surprises you: the branch that ran is the first one that fitted, not the one that fits best. Every pattern after a wildcard is unreachable, which is why the wildcard belongs at the bottom.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What decides which branch of a match runs?
