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.
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.
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
.
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 |
.
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.
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.
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.
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.
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.
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.
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.
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.
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#.