F# Pattern Matching
Pattern
Patterns are rules for transforming input data. They are used throughout F# to compare data with a logical structure or structures, decompose data into constituent parts, or extract information from data in various ways.
Name | Description | Example |
---|---|---|
Constant pattern | Any numeric, character, or string literal, an enumeration constant, or a defined literal identifier | 1.0, “test”, 30, Color.Red |
Identifier pattern | A case value of a discriminated union, an exception label, or an active pattern case | Some(x) Failure(msg) |
Variable pattern | identifier | a |
as pattern | pattern as identifier | (a, b) as tuple1 |
OR pattern | pattern1 | pattern2 |
AND pattern | pattern1 & pattern2 | (a, b) & (_, “test”) |
Cons pattern | identifier :: list-identifier | h :: t |
List pattern | [ pattern_1; … ; pattern_n ] | [ a; b; c ] |
Array pattern | [ | pattern_1; ..; pattern_n |
Parenthesized pattern | ( pattern ) | ( a ) |
Tuple pattern | ( pattern_1, … , pattern_n ) | ( a, b ) |
Record pattern | { identifier1 = pattern_1; … ; identifier_n = pattern_n } | { Name = name; } |
Wildcard pattern | _ | _ |
Pattern together with type annotation | pattern : type | a : int |
Type test pattern | :? type [ as identifier ] | :? System.DateTime as dt |
Null pattern | null | null |
Nameof pattern | nameof expr | nameof str |
Constant Patterns
[<Literal>]
let Three = 3
let filter123 x =
match x with
// The following line contains literal patterns combined with an OR pattern.
| 1 | 2 | Three -> printfn "Found 1, 2, or 3!"
// The following line contains a variable pattern.
| var1 -> printfn "%d" var1
for x in 1..10 do filter123 x
// Found 1, 2, or 3!
// Found 1, 2, or 3!
// Found 1, 2, or 3!
// 4
// 5
// 6
// 7
// 8
// 9
// 10
type Color =
| Red = 0
| Green = 1
| Blue = 2
let printColorName (color:Color) =
match color with
| Color.Red -> printfn "Red"
| Color.Green -> printfn "Green"
| Color.Blue -> printfn "Blue"
| _ -> ()
printColorName Color.Red
printColorName Color.Green
printColorName Color.Blue
// Red
// Green
// Blue
Identifier Patterns
let printOption (data : int option) =
match data with
| Some var1 -> printfn "%d" var1
| None -> ()
type PersonName =
| FirstOnly of string
| LastOnly of string
| FirstLast of string * string
let constructQuery personName =
match personName with
| FirstOnly(firstName) -> printfn "May I call you %s?" firstName
| LastOnly(lastName) -> printfn "Are you Mr. or Ms. %s?" lastName
| FirstLast(firstName, lastName) -> printfn "Are you %s %s?" firstName lastName
constructQuery (FirstOnly("john"))
constructQuery (LastOnly("smith"))
constructQuery (FirstLast("john","smith"))
// May I call you john?
// Are you Mr. or Ms. smith?
// Are you john smith?
Variable Patterns
let function1 x =
match x with
| (var1, var2) when var1 > var2 -> printfn "%d is greater than %d" var1 var2
| (var1, var2) when var1 < var2 -> printfn "%d is less than %d" var1 var2
| (var1, var2) -> printfn "%d equals %d" var1 var2
function1 (1,2)
function1 (2, 1)
function1 (0, 0)
// 1 is less than 2
// 2 is greater than 1
// 0 equals 0
as Pattern
The as
pattern is a pattern that has an as clause appended to it.
let (var1, var2) as tuple1 = (1, 2)
printfn "%d %d %A" var1 var2 tuple1
// 1 2 (1, 2)
OR Pattern
The OR pattern is used when input data can match multiple patterns, and you want to execute the same code as a result.
let detectZeroOR point =
match point with
| (0, 0) | (0, _) | (_, 0) -> printfn "Zero found."
| _ -> printfn "Both nonzero."
detectZeroOR (0, 0)
detectZeroOR (1, 0)
detectZeroOR (0, 10)
detectZeroOR (10, 15)
// output
// Zero found.
// Zero found.
// Zero found.
// Both nonzero.
AND Pattern
The AND pattern requires that the input match two patterns. The types of both sides of the AND pattern must be compatible.
let detectZeroAND point =
match point with
| (0, 0) -> printfn "Both values zero."
| (var1, var2) & (0, _) -> printfn "First value is 0 in (%d, %d)" var1 var2
| (var1, var2) & (_, 0) -> printfn "Second value is 0 in (%d, %d)" var1 var2
| _ -> printfn "Both nonzero."
detectZeroAND (0, 0)
detectZeroAND (1, 0)
detectZeroAND (0, 10)
detectZeroAND (10, 15)
// output
// Both values zero.
// Second value is 0 in (1, 0)
// First value is 0 in (0, 10)
// Both nonzero.
Cons Pattern
The cons pattern is used to decompose a list into the first element, the head, and a list that contains the remaining elements, the tail.
let list1 = [ 1; 2; 3; 4 ]
// This example uses a cons pattern and a list pattern.
let rec printList l =
match l with
| head :: tail -> printf "%d " head; printList tail
| [] -> printfn ""
printList list1
List Pattern
The list pattern enables lists to be decomposed into a number of elements. The list pattern itself can match only lists of a specific number of elements.
// This example uses a list pattern.
let listLength list =
match list with
| [] -> 0
| [ _ ] -> 1
| [ _; _ ] -> 2
| [ _; _; _ ] -> 3
| _ -> List.length list
printfn "%d" (listLength [ 1 ])
printfn "%d" (listLength [ 1; 1 ])
printfn "%d" (listLength [ 1; 1; 1; ])
printfn "%d" (listLength [ ] )
// output
// 1
// 2
// 3
// 0
Array Pattern
The array pattern resembles the list pattern and can be used to decompose arrays of a specific length.
// This example uses array patterns.
let vectorLength vec =
match vec with
| [| var1 |] -> var1
| [| var1; var2 |] -> sqrt (var1*var1 + var2*var2)
| [| var1; var2; var3 |] -> sqrt (var1*var1 + var2*var2 + var3*var3)
| _ -> failwith (sprintf "vectorLength called with an unsupported array size of %d." (vec.Length))
printfn "%f" (vectorLength [| 1. |])
printfn "%f" (vectorLength [| 1.; 1. |])
printfn "%f" (vectorLength [| 1.; 1.; 1.; |])
printfn "%f" (vectorLength [| |] )
// output
// 1.000000
// 1.414214
// 1.732051
// System.Exception: vectorLength called with an unsupported array size of 0.
// at FSI_0055.vectorLength(Double[] vec)
// at <StartupCode$FSI_0055>.$FSI_0055.main@()
// Stopped due to error
Tuple Pattern
The tuple pattern matches input in tuple form and enables the tuple to be decomposed into its constituent elements by using pattern matching variables for each position in the tuple.
let detectZeroTuple point =
match point with
| (0, 0) -> printfn "Both values zero."
| (0, var2) -> printfn "First value is 0 in (0, %d)" var2
| (var1, 0) -> printfn "Second value is 0 in (%d, 0)" var1
| _ -> printfn "Both nonzero."
detectZeroTuple (0, 0)
detectZeroTuple (1, 0)
detectZeroTuple (0, 10)
detectZeroTuple (10, 15)
// output
// Both values zero.
// Second value is 0 in (1, 0)
// First value is 0 in (0, 10)
// Both nonzero.
Record Pattern
The record pattern is used to decompose records to extract the values of fields. The pattern does not have to reference all fields of the record; any omitted fields just do not participate in matching and are not extracted.
let IsMatchByName record1 (name: string) =
match record1 with
| { MyRecord.Name = nameFound; MyRecord.ID = _; } when nameFound = name -> true
| _ -> false
let recordX = { Name = "Parker"; ID = 10 }
let isNameMatched1 = IsMatchByName recordX "Parker"
let isNameMatched2 = IsMatchByName recordX "Hartono"
printfn "isNameMatched1 %A isNameMatched2 %A " isNameMatched1 isNameMatched2
// output
// isNameMatched1 true isNameMatched2 false
let IsMatchByNameOrId record1 (name: string, id: int) =
match record1 with
| { MyRecord.Name = nameFound; MyRecord.ID = _; } when nameFound = name -> true
| { MyRecord.ID = myid; MyRecord.Name = _ ;} when myid = id -> true
| _ -> false
let isAnyMatched1 = IsMatchByNameOrId recordX ("Parker", 2)
let isAnyMatched2 = IsMatchByNameOrId recordX ("Hartono", 10)
let isAnyMatched3 = IsMatchByNameOrId recordX ("XXX", 5)
printfn "isAnyMatched1 %A isAnyMatched2 %A isAnyMatched3 %A" isAnyMatched1 isAnyMatched2 isAnyMatched3
// output
// isAnyMatched1 true isAnyMatched2 true isAnyMatched3 false
Patterns That Have Type Annotations
Patterns can have type annotations.
let detect1 x =
match x with
| 1 -> printfn "Found a 1!"
| (var1 : int) -> printfn "%d" var1
detect1 5
detect1 1
// output
// 5
// Found a 1!
Type Test Pattern
The type test pattern is used to match the input against a type.
open System.Windows.Forms
let RegisterControl(control:Control) =
match control with
| :? Button as button -> button.Text <- "Registered."
| :? CheckBox as checkbox -> checkbox.Text <- "Registered."
| _ -> ()
// ------------------------------------------------------------------------
// If you're only checking if an identifier is of a particular derived type,
// you don't need the as identifier part of the pattern
type A() = class end
type B() = inherit A()
type C() = inherit A()
let m (a: A) =
match a with
| :? B -> printfn "It's a B"
| :? C -> printfn "It's a C"
| _ -> ()
Null Pattern
The null pattern matches the null value that can appear when you are working with types that allow a null value.
let ReadFromFile (reader : System.IO.StreamReader) =
match reader.ReadLine() with
| null -> printfn "\n"; false
| line -> printfn "%s" line; true
let fs = System.IO.File.Open("./test.fs", System.IO.FileMode.Open)
let sr = new System.IO.StreamReader(fs)
while ReadFromFile(sr) = true do ()
sr.Close()
// output
// let ReadFromFile (reader : System.IO.StreamReader) =
// match reader.ReadLine() with
// | null -> printfn "\n"; false
// | line -> printfn "%s" line; true
// let fs = System.IO.File.Open("./test.fs", System.IO.FileMode.Open)
// let sr = new System.IO.StreamReader(fs)
// while ReadFromFile(sr) = true do ()
// sr.Close()
Nameof pattern
The nameof pattern matches against a string when its value is equal to the expression that follows the nameof keyword.
let f (str: string) =
match str with
| nameof str -> "It's 'str'!"
| _ -> "It is not 'str'!"
f "str" // matches
f "asdf" // does not match
// output
// It's 'str'!
// It is not 'str'!