Introduction
Within functional programming, pattern matching is a unique feature of F#.
It is common that most programs must sort and filter data; in functional programming, this is done using pattern matching just like a switch case statement in other languages.
OK, let's get started then.
What is Pattern Matching
Before we define pattern matching say that pattern matching is almost similar to the switch statement of C/C++/C# language but F#'s pattern matching is still much more powerful.
Pattern matching is a series of rules that will execute if the pattern matches the input. Then it returns the result of the rule that was matched. Thus, all rules in the pattern match must return the same type.
Syntax of Pattern Matching
match expression with
| pattern1 -> expression1
| pattern2 -> expression2
| pattern3 -> expression3
....
....
| patternN -> expressionN
From the sample syntax have you noticed the vertical bar |? It is used to define a choice in the pattern.
Moreover, we can use the match and with keywords with a series of rules then each followed by an arrow ->.
Note
Unlike the switch statement, the pattern matching expression returns a value.
Examples
Let's try to have a simple example to help us to name a constant.
(* Let's try to create a name constant function*)
(*
This function returns the character equivalent
for
1. π = pi = 3.14
2. e = Euler's number = 2.71
3. ß = Bernstein's constant = 0.28
*)
let letMeGuessTheConstant constant =
match constant with
| 3.14 -> "π"
| 2.71 -> "e"
| 0.28 -> "ß"
| _ -> "Not Available" (* This is a wildcard symbol that matches all possible values *)
The following example above shows using a pattern matching that checks whether the constant float number has an equivalent mathematics symbol.
If you think you can add more constants, let us know in the comment section below.
Note: Just a thought, pattern matching forces developers to list all possible cases, which is advantageous over the traditional switch or if-else statements.
The as Keyword
The as keyword is used to project the value into a variable. Let's see an example on how to do that.
[<Fact>]
let ``Pattern Matching As Keyword``() =
let isOdd num1 =
match num1 with
| (num1) as n ->
match (n % 2 = 1) with
| true -> "It is odd"
| false -> "It is even"
let resultEven = isOdd 2
let resultOdd = isOdd 3
Assert.Equal("It is odd", resultOdd)
Assert.Equal("It is even", resultEven)
As you can see from the example there's a first match which is the (num1) then it is projected to the n variable via the as keyword.
For there the n variable was transferred to another pattern match which verifies if the number is odd or even.
Note
There would be a lot of complex examples to show the use of as keyword but in this example, we made it only simple as possible.
Incomplete Matches
What if the pattern matching didn't cover all possible matches?
- The F# compiler will give you a warning message.
- The program will fail if no pattern matches the given expression.
- A MatchFailureException will be thrown at runtime, let's see an example below.
[<Fact>]
let ``Pattern Matching When All Possible Cases Aren't Covered`` () =
let letterIndex l =
match l with
| 'a' -> 1
| 'b' -> 2
Assert.Throws<MatchFailureException>(fun () -> letterIndex 'c' |> ignore)
Running the code sample above will throw an exception. Although the exception is handled because we have used the method Assert.Throws.
Just remove the Assert.Throws method or put the sample code in a different location of your program the exception will be visible at runtime.
Wildcard Pattern
The underscore _ symbol matches all the possible values that's why it is called the wildcard pattern.
This is beneficial to many developers when we cannot list all possible cases for certain pattern matches.
However, don't abuse the wildcard pattern as it will prevent the compiler from helping us when we forget a case in a matching expression.
Note: If you're coming from the C# language this is similar to the default case in the switch case statement.
Guard Expressions
Using the when the keyword (guard expressions) is evaluated within the context of the pattern.
Let's see an example below.
[<Fact>]
let ``Pattern Matching With Guard Expressions``() =
let isYourAgeLegalAdultHood (age:float) :string =
match age with
| x when x < 0.0 -> "You're not even born yet."
| x when x > 0 && x <0.99 -> "Approxiametly less than 1 year old."
| x when x >= 1 && x <= 17 -> "Still not in legal age."
| x when x >= 18 && x <= 60 -> "Legal Age."
| x when x >= 61 && x <= 80 -> "Congrats still alive."
| x when x >= 81 && x <= 100 -> "Legal age but seniors."
| x when x >= 100 -> "Legal age hopefully your still alive."
| _ -> "Age unable to proces."
let isLegal1 = isYourAgeLegalAdultHood 0.9
Assert.Equal(isLegal1, "Approxiametly less than 1 year old.")
let isLegal2 = isYourAgeLegalAdultHood 10
Assert.Equal(isLegal2, "Still not in legal age.")
let isLegal3 = isYourAgeLegalAdultHood 20
Assert.Equal(isLegal3, "Legal Age.")
let isLegal4 = isYourAgeLegalAdultHood 30
Assert.Equal(isLegal4, "Legal Age.")
let isLegal5 = isYourAgeLegalAdultHood 40
Assert.Equal(isLegal5, "Legal Age.")
let isLegal6 = isYourAgeLegalAdultHood 50
Assert.Equal(isLegal6, "Legal Age.")
let isLegal7 = isYourAgeLegalAdultHood 80
Assert.Equal(isLegal7, "Congrats still alive.")
let isLegal8 = isYourAgeLegalAdultHood 90
Assert.Equal(isLegal8, "Legal age but seniors.")
let isLegal9 = isYourAgeLegalAdultHood 150
Assert.Equal(isLegal9, "Legal age hopefully your still alive.")
From the example above we have seen the guard's expressions.
In our example we have just checked whether the age of the person belongs to a certain range then we’ll give a remark about where his or her age belongs to.
Summary
In this article, we've discussed pattern matching and the following:
- Syntax
- The as keyword
- Incomplete matches
- Wildcard pattern
- Guard expressions
I hope you have enjoyed this article, as I enjoyed it while writing.
You can also find the sample code here on GitHub. Till next time, happy programming!