Active Patterns
Prerequisites: Pattern matching in depth
Active patterns let you define your own patterns — functions that
match can call to classify or take apart a value. They're one of F#'s
most distinctive features.
Complete active patterns
A pattern with several cases, written in "banana clips" (| ... |),
splits every input into one of the cases:
let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd
let (|Negative|Zero|Positive|) n =
if n < 0 then Negative elif n = 0 then Zero else Positive
let describe n =
match n with
| Zero -> "zero"
| Negative & Even -> "negative even"
| Negative -> "negative odd"
| Positive & Even -> "positive even"
| Positive -> "positive odd"
for n in [-4; -3; 0; 7; 10] do
printfn "%d: %s" n (describe n)
-4: negative even
-3: negative odd
0: zero
7: positive odd
10: positive even
& combines patterns: the value must match both.
Partial active patterns
A pattern that may not match ends with |_| and returns an option:
let (|Int|_|) (s: string) =
match System.Int32.TryParse s with
| true, n -> Some n
| _ -> None
let (|Bool|_|) (s: string) =
match s.ToLower() with
| "true" | "yes" -> Some true
| "false" | "no" -> Some false
| _ -> None
let parse input =
match input with
| Int n -> $"integer {n}"
| Bool b -> $"boolean %b{b}"
| other -> $"text '{other}'"
["42"; "Yes"; "hello"] |> List.iter (parse >> printfn "%s")
integer 42
boolean true
text 'hello'
The messy parsing code is hidden in the patterns, and the match reads
like a description of the input format.
Parameterized patterns and regexes
Active patterns can take extra arguments before the value. A regex pattern is the classic example:
open System.Text.RegularExpressions
let (|Regex|_|) pattern input =
let m = Regex.Match(input, pattern)
if m.Success then Some [ for g in m.Groups |> Seq.skip 1 -> g.Value ]
else None
let parseCommand line =
match line with
| Regex @"^move (\d+) (\d+)$" [ x; y ] -> $"move to {x},{y}"
| Regex @"^say (.+)$" [ text ] -> $"say \"{text}\""
| Regex @"^quit$" [] -> "quit"
| _ -> "unknown command"
[ "move 3 4"; "say hello world"; "quit"; "jump" ]
|> List.iter (parseCommand >> printfn "%s")
move to 3,4
say "hello world"
quit
unknown command
Single-case patterns
A single-case active pattern transforms a value — handy for normalizing input right in a parameter:
let (|Trimmed|) (s: string) = s.Trim().ToLower()
let isYes (Trimmed answer) = answer = "yes" || answer = "y"
printfn "%b %b %b" (isYes " YES ") (isYes "y") (isYes "nope")
true true false
Practice
Write a partial active pattern (|DivisibleBy|_|) d n that matches when
n is divisible by d, and use it for FizzBuzz from 1 to 15.
Show solution
let (|DivisibleBy|_|) d n = if n % d = 0 then Some () else None
let fizzBuzz n =
match n with
| DivisibleBy 15 -> "FizzBuzz"
| DivisibleBy 3 -> "Fizz"
| DivisibleBy 5 -> "Buzz"
| _ -> string n
[1 .. 15] |> List.map fizzBuzz |> String.concat " " |> printfn "%s"
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz
A partial pattern that carries no value returns Some (). (Since F# 9
it may also simply return true or false.)