F# Training
F# Training
F# Training
  • Presentation
  • Fundamentals
    • Introduction
    • Syntax
      • Bases
      • Functions
      • Rules
      • Exceptions
    • First concepts
    • šŸ”Quiz
  • Functions
    • Signature
    • Concepts
    • Syntax
    • Standard functions
    • Operators
    • Addendum
    • šŸ”Quiz
    • šŸ“œSummary
  • Types
    • Overview
    • Tuples
    • Records
    • Unions
    • Enums
    • Anonymous records
    • Value types
    • šŸ“œRecap
    • Addendum
  • Monadic types
    • Intro
    • Option type
    • Result type
    • Smart constructor
    • šŸš€Computation expression (CE)
    • šŸš€CE theoretical basements
    • šŸ“œRecap
  • Pattern matching
    • Patterns
    • Match expression
    • Active patterns
    • šŸš€Fold function
    • šŸ“œRecap
    • šŸ•¹ļøExercises
  • Collections
    • Overview
    • Types
    • Common functions
    • Dedicated functions
    • šŸ”Quiz
    • šŸ“œRecap
  • Asynchronous programming
    • Asynchronous workflow
    • Interop with .NET TPL
    • šŸ“œRecap
  • Module and Namespace
    • Overview
    • Namespace
    • Module
    • šŸ”Quiz
    • šŸ“œRecap
  • Object-oriented
    • Introduction
    • Members
    • Type extensions
    • Class, Struct
    • Interface
    • Object expression
    • Recommendations
Powered by GitBook
On this page
  • Wildcard Pattern
  • Constant Pattern
  • Variable Pattern
  • Identifier Pattern
  • OR / AND Patterns
  • Alias Pattern
  • Parenthesized Pattern
  • Construction Patterns
  • Cons and List Patterns
  • Array Pattern
  • Tuple Pattern
  • Record Pattern
  • Type Test Pattern
  • try/with block
  • Boxing

Was this helpful?

Edit on GitHub
  1. Pattern matching

Patterns

Definition

Patterns are rules for detecting input data structure.

Used extensively in F♯

  • match expression, let binding, parameters deconstruction...

  • Very practical for manipulating F♯ algebraic types (tuple, record, union)

  • Composable: supports multiple nesting levels

  • Composed by logical AND/OR

  • Supports literals: 1.0, "test"...

Wildcard Pattern

Represented by _, alone or combined with another pattern.

Always true → To be placed last in a match expression.

āš ļø Always seek 1st to handle all cases exhaustively/explicitly When impossible, then use _

match option with
| Some 1 -> ...
| _ -> ...              // āš ļø Non exhaustive

match option with
| Some 1 -> ...
| Some _ | None -> ...  // šŸ‘Œ More exhaustive

Recommendation

  • Always seek first to handle all cases exhaustively/explicitly

  • When it's impossible (by combination explosion), too ugly to read or too boring to write

  • Then use _ to discard all other cases

Constant Pattern

Detects constants, null and number literals, char, string, enum.

[<Literal>]
let Three = 3   // Constant

let is123 num = // int -> bool
    match num with
    | 1 | 2 | Three -> true
    | _ -> false

ā˜ Notes :

  • The Three pattern is also classified as an Identifier Pattern šŸ“

  • For null matching, we also talk about Null Pattern.

Variable Pattern

Assigns the detected value to a "variable" for subsequent use

Example: b variable below

let isInt (s: string) =
    match System.Int32.TryParse(s) with
    | b, _ -> b

āš ļø You cannot link to the same variable more than once.

let elementsAreEqualKo tuple =
    match tuple with
    | x, x -> true  // šŸ’„ Error FS0038: x' is linked twice in this model
    | _, _ -> false

šŸ’” Solution: use 2 variables then check their equality

// 1. Guard (`when`) šŸ“
let elementsAreEqual = function
    | x, y when x = y -> true
    | _, _ -> false

// 2. Deconstruction: even simpler!
let elementsAreEqual' (x, y) =
    x = y

Identifier Pattern

Detects cases of a union type and their possible contents

type PersonName =
    | FirstOnly of string
    | LastOnly of string
    | FirstLast of string * string

let classify personName =
    match personName with
    | LastOnly _ -> "Last name only"
    | FirstOnly _ -> "First name only"
    | FirstLast _ -> "First and last names"

The identifier pattern can be combined with both constant and variable patterns. When the cases fields have labels, we can pattern match on them too:

type Shape =
    | Circle of radius: int
    | Rectangle of height: int * width: int

let describe shape =
    match shape with
    | Circle radius -> $"Circle āˆ… %i{2*radius}"

    // 1. "Anonymous" pattern of the field as tuple
    // āš ļø All elements must be matched, hence the `_` to discard the `width`
    | Rectangle(0, _)

    // 2. Match on a single field by its name
    | Rectangle(width = 0) -> "Flat rectangle"

    // 3. Match on several fields by name
    // āš ļø We use `;` to separate fields, like for a record, even though the union fields are closer to tuples.
    | Rectangle(width = w; height = h) -> $"Rectangle %i{w} Ɨ %i{h}"

OR / AND Patterns

Combine two patterns (named P1 and P2 below):

  • P1 | P2 → P1 or P2 - E.g. Rectangle (0, _) | Rectangle (_, 0)

  • P1 & P2 → P1 and P2 - used especially with active patterns šŸ“

type Upload = { Filename: string; Title: string option }

let titleOrFile ({ Title = Some name } | { Filename = name }) = name

titleOrFile { Filename = "Report.docx"; Title = None }            // Report.docx
titleOrFile { Filename = "Report.docx"; Title = Some "Report+" }  // "Report+"

ā˜ļø Explanations:

  • The Title is optional. The first pattern { Title = Some name } will match only if the Title is specified.

  • The second pattern { Filename = name } will always works. It's written to extract the Filename into the same variable name as the first pattern.

Warning

This code is a bit convoluted, meant for demo purpose, too "smart" for maintenable code.

Alias Pattern

as is used to name an element whose content is deconstructed

let (x, y) as coordinate = (1, 2)
printfn "%i %i %A" x y coordinate  // 1 2 (1, 2)

šŸ’” Also works within functions to get back the parameter name, person below:

type Person = { Name: string; Age: int }

let acceptMajorOnly ({ Age = age } as person) = // person: Person -> Person option
    if age < 18 then None else Some person

šŸ’” The AND pattern can be used as an alternative to the alias pattern. ā˜ļø However, it won't work to get the parameter name:

type Person = { Name: string; Age: int }

let acceptMajorOnly' ({ Age = age } & person) = // Person -> Person option
    if age < 18 then None else Some person

Parenthesized Pattern

Purpose: Use of parentheses () to group patterns

Common use case: tackle precedence

type Shape = Circle of Radius: int | Square of Side: int

let countFlatShapes shapes =
    let rec loop rest count =
        match rest with
        | (Square(Side = 0) | (Circle(Radius = 0))) :: tail -> loop tail (count + 1) // ā‘ 
        | _ :: tail -> loop tail count
        | [] -> count
    loop shapes 0

ā˜ Line ā‘  would compile without doing () :: tail āš ļø Parentheses complicate reading šŸ’” Try to do without when possible:

// 1. Repeat `tail` variable
let countFlatShapes shapes =
    let rec loop rest count =
        match rest with
        | Circle(Radius = 0) :: tail
        | Square(Side = 0) :: tail -> loop tail (count + 1)
        // [...]

// 2. Extract isFlatShape function
let isFlat =
    function
    | Circle(Radius = 0)
    | Square(Side = 0) -> true
    | _ -> false

let countFlatShapes shapes =
    let rec loop rest count =
        match rest with
        | shape :: tail when shape |> isFlat -> loop tail (count + 1)
        // [...]

Construction Patterns

Use type construction syntax to deconstruct a type

  • Cons and List Patterns

  • Array Pattern

  • Tuple Pattern

  • Record Pattern

Cons and List Patterns

ā‰ƒ Inverses of the 2 ways to construct a list

Cons Pattern : head :: tail → decomposes a list (with >= 1 element) into:

  • Head: 1st element

  • Tail: another list with the remaining elements - can be empty

List Pattern : [items] → decompose a list into 0..N items

  • [] : empty list

  • [x] : list with 1 element set in the x variable

  • [x; y] : list with 2 elements set in variables x and y

  • [_; _]: list with 2 elements ignored

šŸ’” x :: [] ≔ [x], x :: y :: [] ≔ [x; y]...

The default match expression combines the 2 patterns: → A list is either empty [], or composed of an item and the rest: head :: tail

Example: → Recursive functions traversing a list → The [] pattern is used to stop recursion:

[<TailCall>]
let rec printList l =
    match l with
    | head :: tail ->
        printf "%d " head
        printList tail
    | [] -> printfn ""

Array Pattern

Syntax: [| items |] for 0..N items between ;

let length vector =
    match vector with
    | [| x |] -> x
    | [| x; y |] -> sqrt (x*x + y*y)
    | [| x; y; z |] -> sqrt (x*x + y*y + z*z)
    | _ -> invalidArg (nameof vector) $"Vector with more than 4 dimensions not supported"

ā˜ There is no pattern for sequences, as they are "lazy ".

Tuple Pattern

Syntax: items or (items) for 2..N items between ,.

šŸ’” Useful to match several values at the same time

type Color = Red | Blue
type Style = Background | Text

let css color style =
    match color, style with
    | Red, Background -> "background-color: red"
    | Red, Text -> "color: red"
    | Blue, Background -> "background-color: blue"
    | Blue, Text -> "color: blue"

Record Pattern

Syntax: { Field1 = var1; ... }

  • It's not required to specify all fields in the record

  • In case of ambiguity on the record type, qualify the field: Record.Field

šŸ’” Also works for function parameters:

type Person = { Name: string; Age: int }

let displayMajority { Age = age; Name = name } =
    if age >= 18
    then printfn "%s is major" name
    else printfn "%s is minor" name

let john = { Name = "John"; Age = 25 }
displayMajority john // John is major

āš ļø Reminder: there is no pattern for anonymous Records!

type Person = { Name: string; Age: int }

let john = { Name = "John"; Age = 25 }
let { Name = name } = john  // šŸ‘Œ val name : string = "John"

let john' = {| john with Civility = "Mister" |}
let {| Name = name' |} = john'  // šŸ’„

Type Test Pattern

Syntax: my-object :? sub-type and returns a bool → ā‰ƒ my-object is sub-type in C♯

Usage: with a type hierarchy

open System.Windows.Forms

let RegisterControl (control: Control) =
    match control with
    | :? Button as button -> button.Text <- "Registered."
    | :? CheckBox as checkbox -> checkbox.Text <- "Registered."
    | :? Windows -> invalidArg (nameof control) "Window cannot be registered"
    | _ -> ()
type Car = interface end

type Mercedes() =
    interface Car

let benz = Mercedes()

let t1 = benz :? Mercedes
//       ~~~~~~~~~~~~~~~~ āš ļø
// Warning FS0067: This type test or downcast will always hold

let t2 = benz :? Car
//       ~~~~~~~~~~~ šŸ’„
// Error FS0193: Type constraint mismatch. The type 'Car' is not compatible with type 'Mercedes'

let t3 = box benz :? Car
// val t3: bool = true

try/with block

This pattern is common in try/with blocks:

try
    printfn "Difference: %i" (42 / 0)
with
| :? DivideByZeroException as x -> 
    printfn "Fail! %s" x.Message
| :? TimeoutException -> 
    printfn "Fail! Took too long"

Boxing

The Type Test Pattern only works with reference types. → For a value type or unknown type, it must be boxed.

let isIntKo = function :? int -> true | _ -> false
//                     ~~~~~~
// šŸ’„ Error FS0008: This runtime coercion or type test from type 'a to int
//    involves an indeterminate type based on information prior to this program point.

let isInt x =
    match box x with
    | :? int -> true
    | _ -> false
PreviousRecapNextMatch expression

Last updated 1 month ago

Was this helpful?

ā˜ļø Note: the :? operator is not designed to check evidence, which can be confusing. → Example: (from )

stackoverflow