ZetCode

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:

F# single-case active pattern

Single-case active patterns transform input data into another form.

single_case.fsx
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.

multi_case.fsx
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.

partial.fsx
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.

parameterized.fsx
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.

type_test.fsx
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.

xml.fsx
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.

composition.fsx
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.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.