Pattern matching is a staple of any functional programming language. F# has a decent pattern matching system with 16 matching patterns out of the box. You also have the ability to extend the pattern matches by using Active Patterns. Let’s start with some common pattern matches for F#.
The basic syntax for pattern matching in F# is a
match with expression. The
with is then followed by a vertical bar and a pattern.
match item_to_be_matched with | pattern -> result
This is a single case pattern match. It’s not used much at all. Generally pattern matches have multiple cases. Each pattern is separated by a vertical bar. You can think of the vertical bar as being “OR”.
match item_to_be_matched with | pattern1 -> result1 | pattern2 -> result2 . . . | patternN -> resultN
Using a constant pattern is very similar to a
switch statement in C#. Any type that can be defined as a constant can be used in this type of pattern.
match number with | 0 -> "Zero" | 1 -> "One" | 2 -> "Two"
match role with | "administrator" -> Administrators | "manger" -> Managers | "user" -> Users
Exhaustive vs Incomplete Patterns
A pattern match is considered exhaustive when it accounts for every possible pattern. We don’t always have to use exhaustive patterns, however we do run the risk of receiving an exception if no match is found. If you are using an editor like Visual Studio or Visual Studio Code (with Ionide-Fsharp) you will get highlighting indicating a pattern may not be accounting for every possibility. It’s preferable to use exhaustive patterns to eliminate the possibility of a match failure exception.
Visual Studio Code with Ionide-Fsharp indicating an incomplete pattern.
The wildcard pattern is used as a catch all pattern. This is denoted by an underscore or a generic label in the pattern. Use of an underscore informs the next developer the value is of no importance. The wildcard pattern is very much like the
default clause of a
switch statement in C#. This will take any incomplete pattern and make it exhaustive. We can make the
isZero function exhaustive by adding the wildcard pattern.
let isZero number = match number with | 0 -> true | _ -> false
let greeting name = match name with | "hal" -> printfn "I'm afraid I can't do that Dave" | _ -> printfn "Hello %s" name
You do have to be careful when using the wildcard. If a business requirement changes and you forget to add the new pattern, the wildcard will happily swallow the match thus leaving you with some unintended results.
Pattern matching with constants is very similar to the C#
switch construct. This is where the similarities end until C# 7. As I mentioned at the beginning of this post, F# has 16 different patterns. We’ll be getting into more of those patterns in future posts. Here is a FizzBuzz implementation using the constant pattern match with Tuple deconstruction. Exciting things are on the way.
let printFizzBuzz number = match (number % 3, number % 5) with | (0, 0) -> printfn "FizzBuzz" | (0, _) -> printfn "Fizz" | (_, 0) -> printfn "Buzz" | _ -> printfn "%d" number