ZetCode

F# match expression

last modified September 26, 2023

In this article we show how to work with match expressions in F#.

The match expression provides branching control that is based on the comparison of an expression with a set of patterns. A branch option is called an arm. We can transform input data, decompose data, or extract parts of data.

The match expression uses the match, with, and when keywords.

Match constant patterns

The basic patterns are constants, integers, strings, or enumerations.

main.fsx
let word = "falcon"

let langs =
    [ "Slovak"
      "German"
      "Hungarian"
      "Russian"
      "French" ]

for lang in langs do

    let res =
        match lang with
        | "Slovak" -> "sokol"
        | "German" -> "Falke"
        | "Hungarian" -> "sólyom"
        | "Russian" -> "сокол"
        | "French" -> "faucon"
        | _ -> "unknown"

    printfn $"{word} in {lang} is {res}"

The program prints a translation of the word falcon in a few languages.

let res =
    match lang with
    | "Slovak" -> "sokol"
    | "German" -> "Falke"
    | "Hungarian" -> "sólyom"
    | "Russian" -> "сокол"
    | "French" -> "faucon"
    | _ -> "unknown"

The match expression returns a value. Each arm in a match expression is started with |. The string pattern follows the | character. The return value is specified after ->. The wildcard match _ returns a value for an option that is not recognized.

λ dotnet fsi main.fsx
falcon in Slovak is sokol
falcon in German is Falke
falcon in Hungarian is sólyom
falcon in Russian is сокол
falcon in French is faucon

Matching enums

In the next example, we match enums.

main.fsx
open System

type Day =
    | Monday
    | Tuesday
    | Wednesday
    | Thursday
    | Friday
    | Saturday
    | Sunday


let days = [ Monday; Tuesday; Wednesday; Thursday; 
    Friday; Saturday; Sunday ]

let rnd = new Random()

let res = days 
        |> Seq.sortBy (fun _ -> rnd.Next()) 
        |> Seq.take 3

for e in res do

    match e with
        | Monday -> printfn "%s" "monday"
        | Tuesday ->  printfn "%s" "tuesday"
        | Wednesday ->  printfn "%s" "wednesay"
        | Thursday ->  printfn "%s" "thursday"
        | Friday ->  printfn "%s" "friday"
        | Saturday ->  printfn "%s" "saturday"
        | Sunday ->  printfn "%s" "sunday"

We define a Day enumeration. We randomly pick three Day enumerations.

match e with
    | Monday -> printfn "%s" "monday"
    | Tuesday ->  printfn "%s" "tuesday"
    | Wednesday ->  printfn "%s" "wednesay"
    | Thursday ->  printfn "%s" "thursday"
    | Friday ->  printfn "%s" "friday"
    | Saturday ->  printfn "%s" "saturday"
    | Sunday ->  printfn "%s" "sunday"

In this match expression, we match against enumerations. Here we do not return value; we print messages.

λ dotnet fsi main.fsx
monday
friday
tuesday

F# match guards

Guards are conditions that must be fulfilled inside the arm. Guards are created with when.

main.fsx
let vals = [ 1; -3; 5; 6; 0; 4; -9; 11; 22; -7 ]

for wal in vals do

    match wal with
    | n when n < 0 -> printfn "%d is negative" n
    | n when n > 0 -> printfn "%d is positive" n
    | _ -> printfn "zero"

With pattern match, we categorize values into negative, positive, and zero values.

| _ -> printfn "zero"

With this we create an exhaustive matching.

λ dotnet fsi main.fsx
1 is positive
-3 is negative
5 is positive
6 is positive
zero
4 is positive
-9 is negative
11 is positive
22 is positive
-7 is negative

F# match multiple options

Multiple options can be combined with |.

main.fsx
let grades = ["A"; "B"; "C"; "D"; "E"; "F"; "FX"]

for grade in grades do

    match grade with
    | "A" | "B" | "C" | "D" | "E" | "F" -> printfn "%s" "passed"
    | _ -> printfn "%s" "failed"

We categorize grades into two groups: passed and failed. The passed arm combines all matching values with |.

λ dotnet fsi main.fsx
passed
passed
passed
passed
passed
passed
failed

F# match records

In the next example, we match records.

main.fsx
type User =
    { FirstName: string
      LastName: string
      Occupation: string }

let users =
    [ { FirstName = "John"
        LastName = "Doe"
        Occupation = "gardener" }
      { FirstName = "Jane"
        LastName = "Doe"
        Occupation = "teacher" }
      { FirstName = "Roger"
        LastName = "Roe"
        Occupation = "driver" } ]

for user in users do
    match user with
    | { LastName = "Doe" } -> printfn "%A" user
    | _ -> ()

We have a few users, which are created with records. With record pattern matching, we select all Does.

match user with
| { LastName = "Doe" } -> printfn "%A" user
| _ -> ()

We specify an anonymous record with an attribute as a pattern.

λ dotnet fsi main.fsx
{ FirstName = "John"
  LastName = "Doe"
  Occupation = "gardener" }
{ FirstName = "Jane"
  LastName = "Doe"
  Occupation = "teacher" }

F# match types

With :?, we can match types.

main.fsx
open System.Collections

type User =
    { FirstName: string
      LastName: string
      Occupation: string }

let vals = new ArrayList()
vals.Add(1.2)
vals.Add(22)
vals.Add(true)
vals.Add("falcon")

vals.Add(
    { FirstName = "John"
      LastName = "Doe"
      Occupation = "gardener" }
)

for wal in vals do
    match wal with
    | :? int -> printfn "an integer"
    | :? float -> printfn "a float"
    | :? bool -> printfn "a boolean"
    | :? User -> printfn "a User"
    | _ -> ()

We have a list with different data types. We go through the list and print the type for each of the elements.

λ dotnet fsi main.fsx
a float
an integer
a boolean
a User

The function syntax

When a pattern match is defined in a function, it can be simplified with the function keyword.

main.fsx
open System 

type Choices =
    | A
    | B
    | C

let getVal =
    function
    | 1 -> A
    | 2 -> B
    | _ -> C

let chx =
    [ for _ in 1..7 do
          yield getVal (Random().Next(1, 4)) ]

printfn "%A" chx

In the example, we builda list of choices randomly.

let getVal =
    function
    | 1 -> A
    | 2 -> B
    | _ -> C

This is the simplified pattern match syntax inside a function.

λ dotnet fsi main.fsx
[B; C; A; C; A; B; B]

F# match list pattern

In the next example, we match list patterns.

main.fsx
let vals =
    [ [ 1; 2; 3 ]
      [ 1; 2 ]
      [ 3; 4 ]
      [ 8; 8 ]
      [ 0 ] ]

let twoels (sub: int list) =
    match sub with
    | [ x; y ] -> printfn "%A" [ x; y ]
    | _ -> ()

for sub in vals do
    twoels sub

The program prints all lists that contain two elements.

λ dotnet fsi main.fsx
[1; 2]
[3; 4]
[8; 8]

List comprehension

A match pattern can be used in a list comprehension.

main.fsx
let res =
    [ for e in 1..100 do
          match e with
          | e when e % 3 = 0 -> yield "fizz"
          | e when e % 5 = 0 -> yield "buzz"
          | e when e % 15 = 0 -> yield "fizzbuzz"
          | _ -> yield (string e) ]

printfn "%A" res

We solve the fizz-buzz challenge with a list comprehension. All values are stored in a list.

λ dotnet fsi main.fsx
["1"; "2"; "fizz"; "4"; "buzz"; "fizz"; "7"; "8"; "fizz"; "buzz"; "11"; "fizz";
 "13"; "14"; "fizz"; "16"; "17"; "fizz"; "19"; "buzz"; "fizz"; "22"; "23";
 "fizz"; "buzz"; "26"; "fizz"; "28"; "29"; "fizz"; "31"; "32"; "fizz"; "34";
 "buzz"; "fizz"; "37"; "38"; "fizz"; "buzz"; "41"; "fizz"; "43"; "44"; "fizz";
 "46"; "47"; "fizz"; "49"; "buzz"; "fizz"; "52"; "53"; "fizz"; "buzz"; "56";
 "fizz"; "58"; "59"; "fizz"; "61"; "62"; "fizz"; "64"; "buzz"; "fizz"; "67";
 "68"; "fizz"; "buzz"; "71"; "fizz"; "73"; "74"; "fizz"; "76"; "77"; "fizz";
 "79"; "buzz"; "fizz"; "82"; "83"; "fizz"; "buzz"; "86"; "fizz"; "88"; "89";
 "fizz"; "91"; "92"; "fizz"; "94"; "buzz"; "fizz"; "97"; "98"; "fizz"; "buzz"]

Active patterns

With active patterns we can define named partitions of input data and use these names in a pattern matching expression.

main.fsx
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd

let testnum input =
   match input with
   | Even -> printfn "%d is even" input
   | Odd -> printfn "%d is odd" input

testnum 3
testnum 8
testnum 11

In the active pattern syntax, we define Even and Odd names. These names are later used in the match expression.

let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd

We define the Even and Odd active patterns.

let testnum input =
    match input with
    | Even -> printfn "%d is even" input
    | Odd -> printfn "%d is odd" input

These names are used in the match expression.

λ dotnet fsi main.fsx
3 is odd
8 is even
11 is odd

Regular expressions

We can use regular expression in match patterns.

main.fsx
open System.Text.RegularExpressions

let (|RegEx|_|) p i =
    let m = Regex.Match(i, p)

    if m.Success then
        Some m.Groups
    else
        None

let checkrgx (msg) =
    match msg with
    | RegEx @"\d+" g -> printfn "Digit: %A" g
    | RegEx @"\w+" g -> printfn "Word : %A" g
    | _ -> printfn "Not recognized"


checkrgx "an old falcon"
checkrgx "1984"
checkrgx "3 hawks"

In the example, we use active patterns and regular expression in pattern matching.

λ dotnet fsi main.fsx
Word : seq [an]
Digit: seq [1984]
Digit: seq [3]

Exception handling

Match expressions can be used to handle exceptions.

main.fsx
open System

printf "Enter a number: "

let value = Console.ReadLine()

let n =
    match Int32.TryParse value with
    | true, num -> num
    | _ -> failwithf "'%s' is not an integer" value


let f = function
    | value when value > 0 -> printfn "positive value"
    | value when value = 0 -> printfn "zero"
    | value when value < 0 -> printfn "negative value"
    | _ -> ()

f n

We expect an integer value from the user. We try to parse the input value; if it is not an integer, we fail with an error message.

let n =
    match Int32.TryParse value with
    | true, num -> num
    | _ -> failwithf "'%s' is not an integer" value

TryParse returns true if the parsing was successfull. Therefore, we have the true boolean pattern in the first arm. For the rest we fail with failwithf.

In this article we have worked with match expressions in F#.

Author

My name is Jan Bodnar and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.