Dive C1.05.3
Active patterns
Name a match case with your own logic, then use it like a built-in.
The patterns so far are the ones F# supplies — constants, lists, tuples. An active
pattern lets a programmer add a case of their own: a small function, written in banana brackets
(| … |), that the matcher calls and treats as a pattern. A complete
active pattern such as (|Even|Odd|) sorts every input into one of its named cases; a
partial one such as (|Int|_|) returns an
option and may decline. In a match, the result reads like any
other arm — | Even, | Int v — and that is the point.
Complete active patterns
A complete active pattern lists its cases in its name and returns one of them for every input. The definition
below partitions an int into Even and
Odd. The names inside the brackets become usable patterns — a
match arm can then be written | Even -> ….
let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd // val (|Even|Odd|) : int -> Choice<unit,unit> match 7 with | Even -> "even" | Odd -> "odd" // val it : string = "odd"
int -> Choice<unit,unit>. The case names
Even and Odd act as patterns, not as values of
a discriminated union — F# represents a two-way partition with the built-in
Choice<_,_> type. Returning Even from the function body
is the syntax for "this input falls in the first case".- (| … |)
- banana brackets: the active-pattern definition syntax; the names between them are the cases.
- complete
- every input maps to exactly one named case; the match over it is exhaustive.
- Choice<unit,unit>
- how F# types a two-case complete active pattern — the partition, not a DU you declared.
Partial active patterns
Not every test partitions its input. A partial active pattern ends its name with
|_| and returns an option: Some v to
match and bind v, or None to decline and let the next arm
try. The pattern below wraps Int32.TryParse, turning a string into an
int only when it parses.
let (|Int|_|) (s: string) = match System.Int32.TryParse s with | true, v -> Some v | false, _ -> None // val (|Int|_|) : string -> int option match "42" with | Int v -> v | _ -> -1 // val it : int = 42 match "x" with | Int v -> v | _ -> -1 // val it : int = -1
- (|Int|_|)
- the
|_|marks a partial active pattern — it may not match. - option result
Some vmatches and bindsv;Nonefalls through to the next arm.- fallback
- because it can decline, a partial pattern needs a following arm (here
| _) to stay exhaustive.
A complete active pattern names every case and stays exhaustive; a partial one matches when it can and steps aside when it cannot. Either way, the arm reads like a built-in pattern.
Classify an input
Pick a value below. An integer routes through (|Even|Odd|) to one of two cases; a
string routes through (|Int|_|) and either binds a parsed int
or declines to the fallback. The readout names the matching case and any bound value, and the static table
holds all four answers.
the input routed to its matching active-pattern case
| input | active pattern | case matched | binds / result |
|---|---|---|---|
| 4 | (|Even|Odd|) | | Even | "even" |
| 7 | (|Even|Odd|) | | Odd | "odd" |
| "42" | (|Int|_|) | | Int v | v = 42 |
| "x" | (|Int|_|) | | _ (no match) | -1 (fallback) |
The same match syntax reads four ways. Even and
Int are not built into F#; a definition adds each one — and once defined, they sit
beside | 0 and | x :: rest as equals.
Active pattern
A function presented to the matcher as a named case — complete (a partition) or partial (an option).
F# today
| Even and | Int v read like built-in arms, computed by your code.
Where this leads
Pattern matching is now complete: fixed shapes, guards, and cases you name yourself. The next module shapes
the data these patterns read — discriminated unions and records. A union declares its own
cases, and a match over it is the natural reader; the partial pattern's
option result here is itself a two-case union, which C1.08 takes up as
Option and Result.
References
Sources
- Microsoft — F# documentation — active patterns: complete, partial, and parameterised forms.
- The F# Language Specification — the grammar of active-pattern definitions and their
Choice/optiontyping. - FSharp.Core API reference — the
Choiceandoptiontypes a pattern returns.
Related in this course
- C1.05.1 — Match expressions — the built-in patterns these sit beside.
- C1.05 — Pattern matching — the module hub and its three dives.
- Elixir — Functional core — matching by structure on the BEAM, without an active-pattern equivalent.