Tuples and pattern matching
A tuple groups values by position, and `match` takes a value apart by shape. Together they replace a pile of `if`s with a list of cases the compiler can check.
One value with parts
(3, "books") is a single value holding an int and a string. The parts are positional and unnamed, which is right for a pair that lives inside one function and wrong for anything that travels: .Item1 says nothing to a reader, and that is the moment a record starts to earn its keep.
A tuple is taken apart in a let — let (count, label) = pair — and a function may answer one, which is how a function answers two questions at once. List.map (fun (a, b) -> a + b) is a lambda whose single parameter is a tuple; the pattern in the parameter position is doing the destructuring.
match chooses by shape
match picks a branch by the shape of a value. Each branch is a pattern, an arrow, and the answer; the patterns are tried in order and the first that fits wins. A pattern can be a literal, a name, a tuple of patterns, or _ for a part you are not using.
Order matters: (0, 0) has to come before (0, y), because y matches anything — put the broad one first and the specific branch below it can never run. That is not a gotcha so much as the tool: specific cases first, one general pattern to catch the rest.
What match buys over a chain of ifs is that the compiler checks it. Every pattern has to have the type of the value being matched, and the compiler can tell you when no branch fits at all.
A match over a tuple of two ints cannot list every value, because there are too many, so a final _ or a pair of names is normal. The compiler’s completeness warning matters for unions and lists, where the cases are finite; for tuples it is telling you that some shape is unhandled, not that a case is missing from a type.
A pair, taken apart two ways
let pair = (3, "books")
let (count, label) = pair
let which point =
match point with
| (0, 0) -> "origin"
| (0, _) -> "on the y axis"
| _ -> "somewhere else"Four points and a swapped pair
Run it and read the four descriptions in order. Then add a fifth point at `(7, 0)` and predict which branch it lands in before you run it.
A tuple of two is called a pair, and fst and snd pick its halves — though a pattern is usually clearer. Pairs are also what groupBy hands back: a key and its group, ready to be taken apart by the function that prints one line of a report.
A pattern is a shape with names
Patterns are not a feature of match alone. They appear on the left of a let, in a function parameter, and inside a lambda: let (count, label) = pair and fun (a, b) -> a + b both describe the shape of a value and give its parts names. match is where patterns become a choice between alternatives, but the matching itself is the same operation everywhere.
A literal in a pattern has to equal the value; a lowercase name binds whatever is there; _ accepts anything without naming it. Patterns nest, so (0, (x, y)) describes a pair whose first part is zero and whose second part is another pair. The names a pattern binds are available only in the branch that matched.
The compiler checks coverage. When a match over a union or a list leaves a shape unhandled, it warns and names the missing case. A final _ makes the match complete, at the cost of hiding any case added later, so it belongs at the end and only when the remaining shapes really are the same answer.
Guards and the function shorthand
A guard adds a condition to a pattern: | (x, y) when x = y ->. The shape has to match first, then the guard is tested; if the guard is false, the next branch is tried. A guarded branch does not count towards completeness, because the compiler cannot know whether the condition will hold.
let describe = function | ... is shorthand for a one-argument function that immediately matches its argument. It saves naming the parameter when the only thing the body does is choose a branch, and it is otherwise the same as fun value -> match value with ....
A branch with a bare name matches everything, so placing it above a specific case makes that case unreachable. A tuple function is called with one pair, not with two arguments. And a guard that fails falls through to the next branch rather than failing the program.
Try these next
- Add a branch for
(x, y) when x > yabove the general pair case and check which points it catches. - Write a recursive
totalover a list using[]andhead :: tail, then call it with an empty list. - Turn a three-branch
matchinto afunctionvalue and map it over a list.
A classifier with guards and list patterns
Run it and check each of the five points against its branch. Then add the point `(-2, 5)` and decide which branch it lands in before you run.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does let (a, b) = (1, 2) do?
