F# active patterns
last modified May 3, 2025
In this article, we explore active patterns in F#. Active patterns enable powerful and flexible pattern matching capabilities beyond standard discriminated unions.
An active pattern is a powerful feature in F# that extends traditional pattern matching by allowing developers to define custom rules for decomposing and processing data. Unlike standard pattern matching, which primarily works with built-in types and predefined structures, active patterns enable flexible and domain-specific ways to interpret values. This makes them particularly useful for complex transformations, structured data extraction, and working with non-uniform types while preserving type safety.
Active patterns come in several forms:
- Single-case patterns: These simplify data extraction by matching a single shape of input.
- Multi-case patterns: Allow multiple distinct cases, enabling more expressive decomposition.
- Partial patterns: Handle cases where a match may or may not be successful, often used with option types.
- Parameterized patterns: Accept additional arguments, making them adaptable for dynamic pattern matching scenarios.
F# single-case active pattern
Single-case active patterns transform input data into another form.
let (|ToUpper|) (s: string) = s.ToUpper()
let greet name =
match name with
| ToUpper upper -> printfn "HELLO, %s!" upper
greet "John"
greet "Alice"
We create a simple active pattern that converts strings to uppercase.
let (|ToUpper|) (s: string) = s.ToUpper()
Defines a single-case active pattern that transforms its input.
λ dotnet fsi single_case.fsx HELLO, JOHN! HELLO, ALICE!
F# multi-case active pattern
Multi-case active patterns partition input data into multiple possibilities.
let (|Even|Odd|) n =
if n % 2 = 0 then Even else Odd
let testNumber x =
match x with
| Even -> printfn "%d is even" x
| Odd -> printfn "%d is odd" x
testNumber 4
testNumber 7
We define an active pattern that categorizes numbers as even or odd.
let (|Even|Odd|) n =
The multi-case active pattern splits input into two categories.
λ dotnet fsi multi_case.fsx 4 is even 7 is odd
F# partial active pattern
Partial active patterns match only some inputs, returning option types.
let (|Integer|_|) (s: string) =
match System.Int32.TryParse(s) with
| true, n -> Some n
| _ -> None
let parseInput input =
match input with
| Integer n -> printfn "Got integer: %d" n
| _ -> printfn "Not an integer"
parseInput "42"
parseInput "hello"
Shows a partial active pattern for parsing strings to integers.
let (|Integer|_|) (s: string) =
The |_| indicates this is a partial active pattern that may fail to match.
λ dotnet fsi partial.fsx Got integer: 42 Not an integer
F# parameterized active pattern
Active patterns can take additional parameters beyond the matched value.
let (|DivisibleBy|_|) divisor n =
if n % divisor = 0 then Some DivisibleBy else None
let fizzbuzz n =
match n with
| DivisibleBy 15 _ -> "FizzBuzz"
| DivisibleBy 3 _ -> "Fizz"
| DivisibleBy 5 _ -> "Buzz"
| _ -> string n
[1..20] |> List.map fizzbuzz |> List.iter (printfn "%s")
Demonstrates parameterized active patterns with the FizzBuzz problem.
let (|DivisibleBy|_|) divisor n =
The active pattern takes both a divisor parameter and the value to match.
λ dotnet fsi parameterized.fsx 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz
F# active pattern for type testing
Active patterns can simplify type testing and casting.
let (|IsString|IsInt|IsBool|Other|) (obj: obj) =
match obj with
| :? string as s -> IsString s
| :? int as i -> IsInt i
| :? bool as b -> IsBool b
| _ -> Other
let describe obj =
match obj with
| IsString s -> printfn "String: %s" s
| IsInt i -> printfn "Int: %d" i
| IsBool b -> printfn "Bool: %b" b
| Other -> printfn "Unknown type"
describe (box "hello")
describe (box 42)
describe (box true)
describe (box 3.14)
Shows how active patterns can cleanly handle runtime type testing.
match obj with | :? string as s -> IsString s
The active pattern encapsulates type testing and casting logic.
λ dotnet fsi type_test.fsx String: hello Int: 42 Bool: true Unknown type
F# active pattern for XML parsing
Active patterns can simplify working with complex data structures.
open System.Xml.Linq
let (|Elem|_|) name (el: XElement) =
if el.Name.LocalName = name then Some (el.Elements()) else None
let (|Attr|_|) name (el: XElement) =
match el.Attribute(XName.Get name) with
| null -> None
| attr -> Some attr.Value
let parseBook (el: XElement) =
match el with
| Elem "book" children ->
children |> Seq.iter (fun child ->
match child with
| Elem "title" _ -> printfn "Title: %s" (child.Value)
| Elem "author" _ -> printfn "Author: %s" (child.Value)
| Elem "price" _ -> printfn "Price: %s" (child.Value)
| _ -> printfn "Unknown element")
| _ -> printfn "Not a book element"
let xml = """
<books>
<book>
<title>F# in Depth</title>
<author>John Smith</author>
<price>45.99</price>
</book>
</books>
"""
let doc = XDocument.Parse(xml)
doc.Root.Elements() |> Seq.iter parseBook
Demonstrates using active patterns to parse and process XML data.
let (|Elem|_|) name (el: XElement) =
Active patterns make XML processing code more declarative and readable.
λ dotnet fsi xml.fsx Title: F# in Depth Author: John Smith Price: 45.99
F# active pattern composition
Active patterns can be composed together for more complex matching.
let (|Positive|_|) n = if n > 0 then Some Positive else None
let (|Negative|_|) n = if n < 0 then Some Negative else None
let (|Zero|_|) n = if n = 0 then Some Zero else None
let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd
let describeNumber n =
match n with
| Positive & Even -> printfn "%d is positive and even" n
| Positive & Odd -> printfn "%d is positive and odd" n
| Negative & Even -> printfn "%d is negative and even" n
| Negative & Odd -> printfn "%d is negative and odd" n
| Zero -> printfn "Zero"
describeNumber 4
describeNumber 7
describeNumber -2
describeNumber -3
describeNumber 0
Shows how to combine multiple active patterns using logical AND (&).
Positive & Even
Combines two active patterns to match numbers that are both positive and even.
λ dotnet fsi composition.fsx 4 is positive and even 7 is positive and odd -2 is negative and even -3 is negative and odd Zero
In this article we've explored the power and flexibility of active patterns in F#. They provide a way to extend pattern matching to virtually any scenario while keeping code clean and declarative.