Active patterns

Pattern Matching Limits

Limited number of patterns

Impossibility of factoring the action of patterns with their own guard

  • Pattern1 when Guard1 | Pattern2 when Guard2 -> do πŸ’₯

  • Pattern1 when Guard1 -> do | Pattern2 when Guard2 -> do πŸ˜•

Patterns are not first-class citizens Ex: a function can't return a pattern β†’ Just a kind of syntactic sugar

Patterns interact badly with an OOP style

Origin of Active Patterns

πŸ”— Extensible pattern matching via a lightweight language extension ℹ️ 2007 publication by Don Syme, Gregory Neverov, James Margetson

Integrated into Fβ™― 2.0 (2010)

πŸ’‘ Ideas

  • Enable pattern matching on other data structures

  • Make these new patterns first-class citizens

Syntax

General syntax : let (|Cases|) [arguments] valueToMatch = expression

  1. Function with a special name defined in "bananas" (|...|)

  2. Set of 1..N cases in which to store the valueToMatch parameter

πŸ’‘ Kind of factory function of an "anonymous" union type, defined inline

Types

There are 4 types of active patterns:

Name
Cases
Exhaustive
Parameters

1. Simple Total

1

βœ… Yes

❔ 0+

2. Multiple Total

2+

βœ… Yes

❌ 0

3. Partial

1

❌ No

❌ 0

4. Parametric

1

❌ No

βœ… 1+

πŸ’‘ Partial and total indicate the feasibility of "placing the input value in the box(es)"

  • Partial: there is not always a corresponding box

  • Total: there is always a corresponding box β†’ exhaustive pattern

Simple total active pattern

A.k.a Single-case Total Pattern

Syntax: let (|Case|) [...parameters] value = Case [data] Usage: on-site value adjustment

Can accept parameters ⚠️ Usually more difficult to understand

Another example: extracting the polar form of a complex number

Active pattern total multiple

A.k.a Multiple-case Total Pattern

Syntax: let (|Case1|...|CaseN|) value = CaseI [dataI] ☝ No parameters❗

Partial active pattern

Syntax: let (|Case|_|) value = Some Case | Some data | None β†’ Returns the type 'T option if Case includes data, otherwise unit option β†’ Pattern matching is non-exhaustive β†’ a default case is required

Similar example, where active patterns are written with the Option.ofTuple function:

πŸ’‘ To see how much more readable the code is, let's write a more low-level version of tryParseBoolean where we see:

  • Nesting match expressions

  • Difficulty reading lines 6 and 7 due to double Booleans (true..false, true..true)

Parametric partial active pattern

Syntax: let (|Case|_|) ...arguments value = Some Case | Some data | None

Example 1: leap year β†’ Year divisible by 4 but not by 100, except if divisible by 400

Exemple 2 : Regular expression

πŸ’‘ Usages seen with the next example...

Exemple : Hexadecimal color

Recap

Understanding an active pattern

Understanding how to use an active pattern... ...can be a real intellectual challenge! 😡

πŸ‘‰ Explanations using the previous examples...

Understanding a total active pattern

≃ factory function of an "anonymous" union type

Understanding a partial active pattern

☝ Distinguish parameters (input) from data (output)

Examine the active pattern signature: [...params ->] value -> 'U option

  • N-1 parameters: active pattern parameters

  • Last parameter: value to match

  • Return type: 'U option β†’ data of type 'U

    • when unit option β†’ no data

β†’ Examples

  1. let (|Integer|_|) (s: string) : int option

    • Usage match s with Integer i β†’ i: int is the output data

  2. let (|DivisibleBy|_|) (factor: int) (x: int) : unit option

    • Usage match year with DivisibleBy 400 β†’ 400 is the factor parameter

  3. let (|Regexp|_|) (pattern: string) (value: string) : string list option

    • Usage match s with Regexp "#([0-9...)" [ r; g; b ]

      • "#([0-9...)" is the pattern parameter

      • [ r; g; b ] is the output data β€’ It's a nested pattern: a list of 3 strings

Exercice : fizz buzz with active pattern

Rewrite this fizz buzz using an active pattern DivisibleBy.

Solution

πŸ’‘ The active pattern DivisibleBy 3 does not return any data. It's just the syntactic sugar equivalent of if y |> isDivisibleBy 3 . In such a case, Fβ™― 9 allows the Boolean to be returned directly, rather than having to go through the Option type:

Alternative

  • The 2 solutions are equal. It's a matter of style / personal taste.

  • In Fβ™― 9, no need to do |> boolToOption.

Active patterns use cases

  1. Factor a guard (see previous fizz buzz exercise)

  2. Wrapping a BCL method (see (|Regexp|_|) and below).

  3. Improve expressiveness, help to understand logic (see below)

Expressiveness with active patterns

Active pattern: first-class citizen

An active pattern ≃ function with metadata β†’ first-class citizen in Fβ™―

Last updated

Was this helpful?