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.