7. Patterns
Patterns are used to perform simultaneous case analysis and decomposition on values together with
the match
, try...with
, function
, fun
, and let
expression and declaration constructs. Rules are
attempted in order from top to bottom and left to right. The syntactic forms of patterns are shown
in the subsequent table.
rule :=
pat pattern-guard~opt -> expr -- pattern, optional guard and action
pattern-guard := when expr
pat :=
const -- constant pattern
long-ident pat-param~opt pat~opt -- named pattern
_ -- wildcard pattern
pat as ident -- "as" pattern
pat '|' pat -- disjunctive pattern
pat '&' pat -- conjunctive pattern
pat :: pat -- "cons" pattern
pat : type -- pattern with type constraint
pat , ... , pat -- tuple pattern
struct (pat , ... , pat) -- struct tuple pattern
( pat ) -- parenthesized pattern
list-pat -- list pattern
array-pat -- array pattern
record-pat -- record pattern
:? atomic-type -- dynamic type test pattern
:? atomic-type as ident -- dynamic type test pattern
null -- null-test pattern
attributes pat -- pattern with attributes
list-pat :=
[ ]
[ pat ; ... ; pat ]
array-pat :=
[| |]
[| pat ; ... ; pat |]
record-pat :=
{ field-pat ; ... ; field-pat }
atomic-pat :=
pat : one of
const long-ident list-pat record-pat array-pat ( pat )
:? atomic-type
null _
field-pat := long-ident = pat
pat-param :=
| const
| long-ident
| [ pat-param ; ... ; pat-param ]
| ( pat-param , ..., pat-param )
| long-ident pat-param
| pat-param : type
| <@ expr @>
| <@@ expr @@>
| null
pats := pat , ... , pat
field-pats := field-pat ; ... ; field-pat
rules := '|'~opt rule '|' ... '|' rule
Patterns are elaborated to expressions through a process called pattern match compilation. This reduces pattern matching to decision trees which operate on an input value, called the pattern input. The decision tree is composed of the following constructs:
- Conditionals on integers and other constants
- Switches on union cases
- Conditionals on runtime types
- Null tests
- Value definitions
- An array of pattern-match targets referred to by index
7.1 Simple Constant Patterns
The pattern const
is a constant pattern which matches values equal to the given constant. For
example:
let rotate3 x =
match x with
| 0 -> "two"
| 1 -> "zero"
| 2 -> "one"
| _ -> failwith "rotate3"
In this example, the constant patterns are 0, 1, and 2. Any constant listed in §6.3.1 may be used as a
constant pattern except for integer literals that have the suffixes Q
, R
, Z
, I
, N
, G
.
Simple constant patterns have the corresponding simple type. Such patterns elaborate to a call to
the F# structural equality function FSharp.Core.Operators.(=)
with the pattern input and the
constant as arguments. The match succeeds if this call returns true
; otherwise, the match fails.
Note: The use of
FSharp.Core.Operators.(=)
means that CLI floating-point equality is used to match floating-point values, and CLI ordinal string equality is used to match strings.
7.2 Named Patterns
Patterns in the following forms are named patterns :
Long-ident
Long-ident pat
Long-ident pat-params pat
If long-ident
is a single identifier that does not begin with an uppercase character, it is interpreted
as a variable pattern. During checking, the variable is assigned the same value and type as the
pattern input.
If long-ident
is more than one-character long or begins with an uppercase character (that is, if
System.Char.IsUpperInvariant
is true
and System.Char.IsLowerInvariant
is false
on the first
character), it is resolved by using Name Resolution in Patterns (§14.1.6). This algorithm produces one
of the following:
- A union case
- An exception label
- An active pattern case name
- A literal value
Otherwise, long-ident
must be a single uppercase identifier ident
. In this case, pat
is a variable
pattern. An F# implementation may optionally generate a warning if the identifier is uppercase. Such
a warning is recommended if the length of the identifier is greater than two.
After name resolution, the subsequent treatment of the named pattern is described in the following sections.
7.2.1 Union Case Patterns
If long-ident
from §7.2 resolves to a union case, the pattern is a union case pattern. If long-ident
resolves to a union case Case
, then long-ident
and long-ident pat
are patterns that match pattern
inputs that have union case label Case
. The long-ident
form is used if the corresponding case takes
no arguments, and the long-ident pat
form is used if it takes arguments.
At runtime, if the pattern input is an object that has the corresponding union case label, the data values carried by the union are matched against the given argument patterns.
For example:
type Data =
| Kind1 of int * int
| Kind2 of string * string
let data = Kind1(3, 2)
let result =
match data with
| Kind1 (a, b) -> a + b
| Kind2 (s1, s2) -> s1.Length + s2.Length
In this case, result is given the value 5.
When a union case has named fields, these names may be referenced in a union case pattem. When using pattern matching with multiple fields, semicolons are used to delimit the named fields. For example
type Shape =
| Rectangle of width: float * height: float
| Square of width: float
let getArea (s: Shape) =
match s with
| Rectangle (width = w; height = h) -> w*h
| Square (width = w) -> w*w
7.2.2 Literal Patterns
If long-ident
from §7.2 resolves to a literal value, the pattern is a literal pattern. The pattern is
equivalent to the corresponding constant pattern.
In the following example, the Literal
attribute (§10.2.2) is first used to define two literals, and these
literals are used as identifiers in the match expression:
[<Literal>]
let Case1 = 1
[<Literal>]
let Case2 = 100
let result =
match 1 00 with
| Case1 -> "Case1"
| Case2 -> "Case 2 "
| _ -> "Some other case"
In this case, result
is given the value Case2
.
7.2.3 Active Patterns
If long-ident
from §7.2 resolves to an active pattern case name CaseNamei
then the pattern is an
active pattern. The rules for name resolution in patterns (§14.1.6) ensure that CaseNamei
is
associated with an active pattern function f
in one of the following forms:
(| CaseName |) inp
Single case. The function accepts one argument (the value being matched) and can return any type.
(| CaseName |_|) inp
Partial. The function accepts one argument (the value being matched) and must return a value
of type FSharp.Core.option<_>
(| CaseName1 | ...| CaseNamen |) inp
Multi-case. The function accepts one argument (the value being matched), and must return a
value of type FSharp.Core.Choice<_,...,_>
based on the number of case names. In F#, the
limitation n ≤ 7 applies.
(| CaseName |) arg1 ... argn inp
Single case with parameters. The function accepts n+1
arguments, where the last argument (inp
)
is the value to match, and can return any type.
(| CaseName |_|) arg1 ... argn inp
Partial with parameters. The function accepts n +1 arguments, where the last argument (inp
) is
the value to match, and must return a value of type FSharp.Core.option<_>
.
Other active pattern functions are not permitted. In particular, multi-case, partial functions such as the following are not permitted:
(|CaseName1| ... |CaseNamen|_|)
When an active pattern function takes arguments, the pat-params
are interpreted as expressions
that are passed as arguments to the active pattern function. The pat-params
are converted to the
syntactically identical corresponding expression forms and are passed as arguments to the active
pattern function f
.
At runtime, the function f
is applied to the pattern input, along with any parameters. The pattern
matches if the active pattern function returns v
, ChoicekOfN v
, or Some v
, respectively, when applied
to the pattern input. If the pattern argument pat
is present, it is then matched against v
.
The following example shows how to define and use a partial active pattern function:
let (|Positive|_|) inp = if inp > 0 then Some(inp) else None
let (|Negative|_|) inp = if inp < 0 then Some(-inp) else None
match 3 with
| Positive n -> printfn "positive, n = %d" n
| Negative n -> printfn "negative, n = %d" n
| _ -> printfn "zero"
The following example shows how to define and use a multi-case active pattern function:
let (|A|B|C|) inp = if inp < 0 then A elif inp = 0 then B else C
match 3 with
| A -> "negative"
| B -> "zero"
| C - > "positive"
The following example shows how to define and use a parameterized active pattern function:
let (|MultipleOf|_|) n inp = if inp%n = 0 then Some (inp / n) else None
match 16 with
| MultipleOf 4 n -> printfn "x = 4*%d" n
| _ -> printfn "not a multiple of 4"
An active pattern function is executed only if a left-to-right, top-to-bottom reading of the entire pattern indicates that execution is required. For example, consider the following active patterns:
let (|A|_|) x =
if x = 2 then failwith "x is two"
elif x = 1 then Some()
else None
let (|B|_|) x =
if x=3 then failwith "x is three" else None
let (|C|) x = failwith "got to C"
let f x =
match x with
| 0 -> 0
| A -> 1
| B -> 2
| C -> 3
| _ -> 4
These patterns evaluate as follows:
f 0 // 0
f 1 // 1
f 2 // failwith "x is two"
f 3 // failwith "x is three"
f 4 // failwith "got to C"
An active pattern function may be executed multiple times against the same pattern input during resolution of a single overall pattern match. The precise number of times that the active pattern function is executed against a particular pattern input is implementation-dependent.
7.3 “As” Patterns
An “as” pattern is of the following form:
pat as ident
The “as” pattern defines ident
to be equal to the pattern input and matches the pattern input
against pat
. For example:
let t1 = (1, 2)
let (x, y) as t2 = t1
printfn "%d-%d-%A" x y t2 // 1- 2 - (1, 2)
This example binds the identifiers x
, y
, and t1
to the values 1
, 2
, and (1,2)
, respectively.
7.4 Wildcard Patterns
The pattern _
is a wildcard pattern and matches any input. For example:
let categorize x =
match x with
| 1 - > 0
| 0 -> 1
| _ -> 0
In the example, if x
is 0
, the match returns 1
. If x
has any other value, the match returns 0
.
7.5 Disjunctive Patterns
A disjunctive pattern matches an input value against one or the other of two patterns:
pat | pat
At runtime, the pattern input is matched against the first pattern. If that fails, the pattern input is matched against the second pattern. Both patterns must bind the same set of variables with the same types. For example:
type Date = Date of int * int * int
let isYearLimit date =
match date with
| (Date (year, 1, 1) | Date (year, 12, 31)) -> Some year
| _ -> None
let result = isYearLimit (Date (2010,12,31))
In this example, result
is given the value true
, because the pattern input matches the second
pattern.
7.6 Conjunctive Patterns
A conjunctive pattern matches the pattern input against two patterns.
pat1 & pat2
For example:
let (|MultipleOf|_|) n inp = if inp%n = 0 then Some (inp / n) else None
let result =
match 56 with
| MultipleOf 4 m & MultipleOf 7 n -> m + n
| _ -> false
In this example, result
is given the value 22
(= 16 + 8), because the pattern input match matches
both patterns.
7.7 List Patterns
The pattern pat :: pat
is a union case pattern that matches the “cons” union case of F# list values.
The pattern []
is a union case pattern that matches the “nil” union case of F# list values.
The pattern [ pat1 ; ... ; patn ]
is shorthand for a series of ::
and empty list patterns
pat1 :: ... :: patn :: []
.
For example:
let rec count x =
match x with
| [] -> 0
| h :: t -> h + count t
let result1 = count [1;2;3]
let result2 =
match [1;2;3] with
| [a;b;c] -> a + b + c
| _ -> 0
In this example, both result1
and result2
are given the value 6
.
7.8 Type-annotated Patterns
A type-annotated pattern specifies the type of the value to match to a pattern.
pat : type
For example:
let rec sum xs =
match xs with
| [] -> 0
| (h : int) :: t -> h + sum t
In this example, the initial type of h
is asserted to be equal to int
before the pattern h
is checked.
Through type inference, this in turn implies that xs
and t
have static type int list
, and sum
has
static type
int list -> int
.
7.9 Dynamic Type-test Patterns
Dynamic type-test patterns have the following two forms:
:? type
:? type as ident
A dynamic type-test pattern matches any value whose runtime type is type
or a subtype of type
. For
example:
let message (x : System.Exception) =
match x with
| :? System.OperationCanceledException -> "cancelled"
| :? System.ArgumentException -> "invalid argument"
| _ -> "unknown error"
If the type-test pattern is of the form :? type as ident
, then the value is coerced to the given type
and ident
is bound to the result. For example:
let findLength (x : obj) =
match x with
| :? string as s -> s.Length
| _ -> 0
In the example, the identifier s
is bound to the value x
with type string
.
If the pattern input has type tyin
, pattern checking uses the same conditions as both a dynamic type-
test expression e :? type
and a dynamic coercion expression e :?> type
where e
has type tyin
. An
error occurs if type
cannot be statically determined to be a subtype of the type of the pattern input.
A warning occurs if the type test will always succeed based on type
and the static type of the pattern
input.
A warning is issued if an expression contains a redundant dynamic type-test pattern, after any coercion is applied. For example:
match box "3" with
| :? string -> 1
| :? string -> 1 // a warning is reported that this rule is "never matched"
| _ -> 2
match box "3" with
| :? System.IComparable -> 1
| :? string -> 1 // a warning is reported that this rule is "never matched"
| _ -> 2
At runtime, a dynamic type-test pattern succeeds if and only if the corresponding dynamic type-test
expression e :? ty
would return true where e
is the pattern input. The value of the pattern is bound
to the results of a dynamic coercion expression e :?> ty
.
7.10 Record Patterns
The following is a record pattern :
{ long-ident1 = pat1 ; ... ; long-identn = patn }
For example:
type Data = { Header:string; Size: int; Names: string list }
let totalSize data =
match data with
| { Header = "TCP"; Size = size; Names = names } -> size + names.Length * 12
| { Header = "UDP"; Size = size } -> size
| _ -> failwith "unknown header"
The long-identi
are resolved in the same way as field labels for record expressions and must
together identify a single, unique F# record type. Not all record fields for the type need to be
specified in the pattern.
7.11 Array Patterns
An array pattern matches an array of a partciular length:
[| pat ; ... ; pat |]
For example:
let checkPackets data =
match data with
| [| "HeaderA"; data1; data2 |] -> (data1, data2)
| [| "HeaderB"; data2; data1 |] -> (data1, data2)
| _ -> failwith "unknown packet"
7.12 Null Patterns
The null pattern null matches values that are represented by the CLI value null. For example:
let path =
match System.Environment.GetEnvironmentVariable("PATH") with
| null -> failwith "no path set!"
| res -> res
Most F# types do not use null
as a representation; consequently, the null pattern is generally used
to check values passed in by CLI method calls and properties. For a list of F# types that use null
as a
representation, see §5.4.8.
7.13 Guarded Pattern Rules
Guarded pattern rules have the following form:
pat when expr
For example:
let categorize x =
match x with
| _ when x < 0 -> - 1
| _ when x < 0 -> 1
| _ -> 0
The guards on a rule are executed only after the match value matches the corresponding pattern.
For example, the following evaluates to 2
with no output.
match (1, 2) with
| (3, x) when (printfn "not printed"; true) -> 0
| (_, y) -> y