Match expression
Similar to a switch
expression in C♯ 8.0 but more powerful thanks to patterns
Syntax:
match test-expression with
| pattern1 [ when condition ] -> result-expression1
| pattern2 [ when condition ] -> result-expression2
| ...
Returns the result of the 1st branch whose pattern "matches" test-expression
☝ Note: all branches must return the same type!
Exhaustivity
A C# switch
must always define a default case.
Otherwise: compile warning, 💥 MatchFailureException
at runtime
Not necessary in a F# match expression if branches cover all cases because the compiler checks for completeness and "dead" branches
let fn x =
match x with
| Some true -> "ok"
| Some false -> "ko"
| None -> ""
| _ -> "?"
// ~ ⚠️ Warning FS0026: his rule will never be matched
☝ Tip: the more branches are exhaustive, the more code is explicit and safe
Example: checking all the cases of a union type allows you to manage the addition of a case by a warning at compile time:
Warning FS0025: Special criteria incomplete in this expression
Detection of accidental addition
Identification of the code to change to handle the new case
Guard
Syntax: pattern1 when condition
Usage: to refine a pattern, using constraints on variables
let classifyBetween low top value =
match value with
| x when x < low -> "Inf"
| x when x = low -> "Low"
| x when x = top -> "Top"
| x when x > top -> "Sup"
| _ -> "Between"
let test1 = 1 |> classifyBetween 1 5 // "Low"
let test2 = 6 |> classifyBetween 1 5 // "Sup"
💡 The guard is only evaluated if the pattern is satisfied.
Guard vs OR Pattern
The OR pattern has a higher precedence/priority than the Guard :
type Parity = Even of int | Odd of int
let parityOf value =
if value % 2 = 0 then Even value else Odd value
let hasSquare square value =
match parityOf square, parityOf value with
| Even x2, Even x
| Odd x2, Odd x
when x2 = x*x -> true // 👈 The guard is covering the 2 previous patterns
| _ -> false
let test1 = 2 |> hasSquare 4 // true
let test2 = 3 |> hasSquare 9 // true
Pattern composition
Pattern matching is powerful not only due to its exhaustivity but also because of the pattern composition.
→ Example:
match feature with
| { Value = FeatureValue.Boolean(Some true) } -> "Enabled"
| _ -> "Disabled"
Notes
This example demonstrates how we match the data on 4 levels:
feature
is a record with theValue
field (possibly amongst other fields)Value
has the caseBoolean
of the union typeFeatureValue
.This union case contains an optional boolean (
bool option
type).We detect when the provided value is
true
.
Match function
Syntax:
function
| pattern1 [ when condition ] -> result-expression1
| pattern2 [ when condition ] -> result-expression2
| ...
Equivalent to a lambda taking an implicit parameter which is "matched":
fun value ->
match value with
| pattern1 [ when condition ] -> result-expression1
| pattern2 [ when condition ] -> result-expression2
| ...
Benefits:
In a pipeline
value
|> is123
|> function
| true -> "ok"
| false -> "ko"
Terser function
let is123 = function
| 1 | 2 | 3 -> true
| _ -> false
Limitations
As the parameter is implicit, it can make the code more difficult to understand! → See Point-free style
Example: a function with both explicit parameters and the function
keyword
→ The number of parameters and their order can be wrong:
let classifyBetween low high = function // 👈 3 parameters : `low`, `high`, and another one implicit
| x when x < low -> "Inf"
| x when x = low -> "Low"
| x when x = high -> "High"
| x when x > high -> "Sup"
| _ -> "Between"
let test1 = 1 |> classifyBetween 1 5 // "Low"
let test2 = 6 |> classifyBetween 1 5 // "Sup"
Exhaustivity in OOP
The equivalent of the pattern matching exhaustivity in FP is ... the Visitor design pattern in OOP
Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.
→ It's FP in OOP, much very convoluted.
Last updated
Was this helpful?