Introduction
Pattern matching is idiomatic in functional programming. It's an important tool in F# programming, general constructs that combines decompositions and control. Pattern matching provides a concise but clear and highly expressive way of writing conditional logic. Pattern matching allows you to look at the values of an identifier and then make different computations depending on its value. Pattern matching is like a switch statement in C and C# but its more powerful and flexible than a switch statement.
Pattern matching is used for control flow, it allows a programmer to look at a value, test it against a series of conditions and perform certain computations depending on whether that condition is met. While pattern matching is conceptually the same as a series of If-then statements in other languages, pattern matching is used to test whether the piece under test has a desired state, find relevant information or substitute parts with other parts. Programs that are written in the functional style tend to be written as a series of transformations applied to the input data.
Pattern matching allows you to analyze the input data and decide which transformation is to be applied to it. Essentially pattern matching takes an input and a set of rules. Each rules tests the input against a pattern and returns a result if they match. If you want to use pattern matching with explicit values, you can use the match and with keywords. This allows you to match with a particular parameter with some conditions. So for just starting out in F# it's important to know your way around patterns and pattern matching.
Syntax for Pattern Matching
//Syntax for Pattern Matching
match expr with
| pat1 -> result1
| pat2 -> result2
| pat3 when expr2 -> result3
| _ -> defaultResult
In the above syntax every (|) defines a condition and the symbol (->) means "if the given condition is true, return the value" and the symbol (_) means it matches with everything as it's the default pattern.
Match Expression
The match expression is introduced by the keyword match, followed by the expression to be matched, followed by the with keyword. Every condition is then written on a separate line, beginning with (|) and indented until the match keyword. Match is an expression not a statement like switch. In F# match conditions are not values, they are patterns. Constant values like 1, 2 and 3 are simple patterns. It also contains some other patterns like a Wildcard Pattern represented by an Underscore. When a match expression is evaluated it runs through the list of conditions checking the arguments matches with a given pattern.
Pattern Matching Example
// Example of LucasTheorem for Pattern Matching
let rec lucasTheorem a =
match a with
| a when a <= 0 -> failwith "The value should be greater than 0"
| 1 -> 1
| 2 -> 3
| x -> lucasTheorem (a - 1) + lucasTheorem (a - 2)
// call the function and print the results
printfn "(lucasTheorem 2) = %i" (lucasTheorem 2)
printfn "(lucasTheorem 6) = %i" (lucasTheorem 6)
printfn "(lucasTheorem 11) = %i" (lucasTheorem 11)
printfn "(lucasTheorem 12) = %i" (lucasTheorem 12)
Output-
In the above example the second two cases are literals; the values 1 and 2 and these will be replaced with the values 1 and 3 respectively. The fourth case will match any value of x greater than 2 and this will call two further calls to the lucasAlgorithm function.
The rules are matched in the order they are defined, and the compiler will issue an error if pattern matching is incomplete, that is if any possible value that will not match with any value.
Different Ways to form Pattern
General Form |
Type |
Example |
[| pat; ...;pat |] |
Array Pattern |
[|"cmd"; arg1; arg2 |] |
pat & pat |
"And" pattern |
[p] [point(a,b)] |
{id=pat; ...; id=pat} |
Record pattern |
{a=1; b=2} |
[pat; ...;pat] |
List pattern |
[a;b;c] |
pat | pat |
"Or" pattern |
[a] | ["A";a] |
(pat, ... ,pat) |
Tuple pattern |
(1,2("3",a)) |
Null |
Null test Pattern |
Null |
id |
Variable Pattern |
A |
Any literal |
Constant Pattern |
30, "30", 20L, Sysem.DayOfWeek.Monday |
pat as id |
Named Pattern |
[a] as inp |
Tag(pat, ...,pat) |
Tagged union or active Pattern |
point(a,b) |
_ |
Wildcard Pattern |
_ |
:? Type |
Type test Pattern |
:? string |
Use of When in Pattern Matching
You can add a when guard to give precise control when a rule fires. A when guard is made with the keyword When followed by a Boolean expression. Once a rule is matched the when clause is evaluated and the rule will fire only when the expression evaluates to true. The first part of the rule is an identifier that will match any integer but the when guard means the rule will match only those integers that are less than or equal to 0.
This is beneficial when the pattern matching is small and you want to fit it on one line.
Example-
let booleanToString x =
match x with false -> "False" | _ -> "True"
Example for Pattern Matching over Tuples, with Boolean "or" and "and" function pattern matching.
//example for Tuple pattern matching with "and" and "or"
let oneOr a1 a2 =
match a1, a2 with
| true, _ -> true
| _, true -> true
| _ -> false
let oneAnd s =
match s with
| true, true -> true
| _ -> false
printfn "(oneOr true false) = %b" (oneOr true false)
printfn "(oneOr false false) = %b" (oneOr false false)
printfn "(oneAnd (true, false)) = %b" (oneAnd (true, false))
printfn "(oneAnd (true, true)) = %b" (oneAnd (true, true))
Output-
Summary
In this article I have covered Pattern Matching and different forms of patterns.