Every F# programmer is likely familiar with pattern matching using the match keyword, as illustrated below:
match x with
| Some 0 -> "Zero"
| Some n when n > 0 -> "Positive"
| Some n when n < 0 -> "Negative"
| None -> "Unknown"
However, a less well-known fact is that pattern matching can be employed in various other contexts. It can be utilized in:
Full pattern matches with multiple cases
Using the match keyword
Using the function keyword
Single-case patterns
As parameters in function definitions
In lambda function definitions, where parameters come after the fun keyword
On the left side of let bindings
Even seemingly simple constructs, such as:
let a, b = pair
or
let foo (a, b) = ...
utilize single-case pattern matching to destructure tuples.
As demonstrated in Tip #1, this technique can also be applied to single-case Discriminated Unions (DUs). However, attempting to use an incomplete pattern match results in a warning. For instance:
let [ x; y ] = someList
produces a warning stating:
Incomplete pattern matches on this expression. For example, the value '[;;_]' may indicate a case not covered by the pattern(s).
Converting this into a complete pattern match is possible only by rewriting it using match or function. Nevertheless, in quick scripts where warnings and the possibility of exceptions are acceptable, this method provides a convenient way to destructure lists or arrays.
A powerful combination involves using single-case patterns with single-case active patterns to achieve elegant conversions without the need for intermediate bindings. In a C# interop scenario, for instance, an active pattern can be defined for Option.ofObj to safely handle values that could be null:
let (|FromNull|) = Option.ofObj
let workWithNull (FromNull maybeValue) = ...
In this example, maybeValue is of type option, and the auto-documented parameter indicates that we expect a value that could be null. This approach can be extended to various scenarios such as handling Nullable types, auto-parsing of strings, and validation.