F# tips weekly #2: Single case pattern match

F# tips weekly #2: Single case pattern match

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.

📆
We will delve into active patterns in more detail in future posts.