Dive C1.06.2
Discriminated unions
A value is case A or case B — one of a closed set, each carrying its own data.
A discriminated union is F#'s sum type. The declaration
type Shape = Circle of float | Rectangle of float * float names a type whose values are
one of a fixed set of cases. Each case is a distinct shape, and each may carry data — a
Circle carries one float, a
Rectangle carries two. A value is built by naming a case and supplying its data, and it
is taken apart by match, which binds the carried data per case. Because the set of cases
is closed, the compiler checks exhaustiveness: leave a case out of a
match and it warns. Unions may be recursive — a tree references
itself — or hold a single case to give a primitive a domain name.
A closed set of cases
Each line after the | is a case. A case with no of carries
nothing; Circle of float carries one value; Rectangle of float * float
carries a pair. To build a value, write the case name and its data: Circle 1.0,
Rectangle (3.0, 4.0).
type Shape = | Circle of float | Rectangle of float * float let c = Circle 1.0 let r = Rectangle (3.0, 4.0) // both are values of type Shape
A match takes the value apart. One branch per case names the case and binds the data
it carries; the body computes from those bindings. The compiler reads the case list off the type and confirms
every case is handled.
let area s = match s with | Circle r -> System.Math.PI * r * r | Rectangle (w, h) -> w * h // val area : Shape -> float area (Rectangle (3.0, 4.0)) // val it : float = 12.0
- discriminated union
- a sum type: a value is exactly one of a closed set of named cases.
- case
- one alternative — a name, optionally with
ofand the data it carries. - match
- takes a value apart by case, binding the carried data in each branch.
- exhaustiveness
- the compiler warns if a
matchomits a case the type allows.
One value, one matched case
Pick a Shape value below. The diagram lights the case the
match selects, and the readout shows which branch ran and the
float it returned. Every number is the value
area computes — the same one FSI prints.
match s with · the matched case lights up
| value | matched case | area |
|---|---|---|
| Circle 1.0 | Circle r | 3.141592654 |
| Circle 2.0 | Circle r | 12.56637061 |
| Rectangle (3.0, 4.0) | Rectangle (w, h) | 12.0 |
The value names its case; the match picks the matching branch and binds the
data it carries. Add a case to the type and the compiler points at every match that has
not handled it yet.
Sum type
A value is case A or case B or case C — one of a closed set, each carrying its own data.
F# union
Circle of float | Rectangle of float * float — matched exhaustively, the data bound per case.
Recursive and single-case unions
A case may carry the union's own type, which makes the union recursive. A binary tree is the
classic shape: a Node holds a left subtree, a value, and a right subtree, and a
Leaf ends a branch. A function over the tree matches the two cases and recurses on the
subtrees.
type Tree = | Leaf | Node of Tree * int * Tree // a case carries Tree itself let rec sumTree t = match t with | Leaf -> 0 | Node (l, v, r) -> sumTree l + v + sumTree r
A union with one case wraps a primitive to give it a domain type. A bare string says
nothing about what the string is; Email below is a distinct type, so a function that
takes an email cannot be handed a plain string by mistake.
type Email = Email of string let e = Email "alex@example.com" // val it : Email = Email "alex@example.com"
Circle of radius: float. The name documents the field and can be used at the call site
(Circle(radius = 1.0)), without changing the case's shape.
Where this leads
Records are the and; unions are the or. The next dive composes them for a purpose: when a union lists only the combinations a domain allows, the values it admits and the values the domain allows are the same set — so the wrong value cannot be written, and the compiler keeps the rule.
References
Sources
- Microsoft — F# documentation — discriminated unions, recursive unions, and single-case unions.
- The F# Language Specification — union type definitions, construction, and match exhaustiveness.
- The F# Software Foundation — the language's home and learning resources.
Related in this course
- C1.06.3 — Making illegal states unrepresentable — the union turned into a domain guarantee.
- C1.06.1 — Records — the product type that pairs with the union.
- Elixir — Algebraic data types — sums as tagged tuples on the BEAM.