Match expressions

The workhorse of F#

Pattern matching is ubiquitous in F#. It is used for binding values to expressions with let, and in function parameters, and for branching using the match..with syntax.

We have briefly covered binding values to expressions in a post in the "why use F#?" series, and it will be covered many times as we investigate types.

So in this post, we'll cover the match..with syntax and its use for control flow.

What is a match expression?

We have already seen match..with expressions a number of times. And we know that it has the form:

match [something] with 
| pattern1 -> expression1
| pattern2 -> expression2
| pattern3 -> expression3

If you squint at it just right, it looks a bit like a series of lambda expressions:

match [something] with 
| lambda-expression-1
| lambda-expression-2
| lambda-expression-3

Where each lambda expression has exactly one parameter:

param -> expression

So one way of thinking about match..with is that it is a choice between a set of lambda expressions. But how to make the choice?

This is where the patterns come in. The choice is made based on whether the "match with" value can be matched with the parameter of the lambda expression. The first lambda whose parameter can be made to match the input value "wins"!

So for example, if the param is the wildcard _, it will always match, and if first, always win.

Order is important!

Looking at the following example:

We can see that there are three lambda expressions to match, in this order:

So, the 1 pattern gets tried first, then then the 2 pattern, and finally, the _ pattern.

On the other hand, if we changed the order to put the wildcard first, it would be tried first and always win immediately:

In this case, the F# compiler helpfully warns us that the other rules will never be matched.

So this is one major difference between a "switch" or "case" statement compared with a match..with. In a match..with, the order is important.

Formatting a match expression

Since F# is sensitive to indentation, you might be wondering how best to format this expression, as there are quite a few moving parts.

The post on F# syntax gives an overview of how alignment works, but for match..with expressions, here are some specific guidelines.

Guideline 1: The alignment of the | expression clauses should be directly under the match

This guideline is straightforward.

Guideline 2: The match..with should be on a new line

The match..with can be on the same line or a new line, but using a new line keeps the indenting consistent, independent of the lengths of the names:

Guideline 3: The expression after the arrow -> should be on a new line

Again, the result expression can be on the same line as the arrow, but using a new line again keeps the indenting consistent and helps to separate the match pattern from the result expression.

Of course, when all the patterns are very compact, a common sense exception can be made:

match..with is an expression

It is important to realize that match..with is not really a "control flow" construct. The "control" does not "flow" down the branches, but instead, the whole thing is an expression that gets evaluated at some point, just like any other expression. The end result in practice might be the same, but it is a conceptual difference that can be important.

One consequence of it being an expression is that all branches must evaluate to the same type -- we have already seen this same behavior with if-then-else expressions and for loops.

You cannot mix and match the types in the expression.

You can use match expressions anywhere

Since they are normal expressions, match expressions can appear anywhere an expression can be used.

For example, here's a nested match expression:

And here's a match expression embedded in a lambda:

Exhaustive matching

Another consequence of being an expression is that there must always be some branch that matches. The expression as a whole must evaluate to something!

That is, the valuable concept of "exhaustive matching" comes from the "everything-is-an-expression" nature of F#. In a statement oriented language, there would be no requirement for this to happen.

Here's an example of an incomplete match:

The compiler will warn you if it thinks there is a missing branch. And if you deliberately ignore the warning, then you will get a nasty runtime error (MatchFailureException) when none of the patterns match.

Exhaustive matching is not perfect

The algorithm for checking that all possible matches are listed is good but not always perfect. Occasionally it will complain that you have not matched every possible case, when you know that you have. In this case, you may need to add an extra case just to keep the compiler happy.

Using (and avoiding) the wildcard match

One way to guarantee that you always match all cases is to put the wildcard parameter as the last match:

You see this pattern frequently, and I have used it a lot in these examples. It's the equivalent of having a catch-all default in a switch statement.

But if you want to get the full benefits of exhaustive pattern matching, I would encourage you not to use wildcards, and try to match all the cases explicitly if you can. This is particularly true if you are matching on the cases of a union type:

By being always explicit in this way, you can trap any error caused by adding a new case to the union. If you had a wildcard match, you would never know.

If you can't have every case be explicit, you might try to document your boundary conditions as much as possible, and assert an runtime error for the wildcard case.

Types of patterns

There are lots of different ways of matching patterns, which we'll look at next.

For more details on the various patterns, see the MSDN documentation.

Binding to values

The most basic pattern is to bind to a value as part of the match:

By the way, I have deliberately left this pattern (and others in this post) as incomplete. As an exercise, make them complete without using the wildcard.

It is important to note that the values that are bound must be distinct for each pattern. So you can't do something like this:

Instead, you have to do something like this:

This second option can also be rewritten using "guards" (when clauses) instead. Guards will be discussed shortly.

AND and OR

You can combine multiple patterns on one line, with OR logic and AND logic:

The OR logic is particularly common when matching a large number of union cases:

Matching on lists

Lists can be matched explicitly in the form [x;y;z] or in the "cons" form head::tail:

A similar syntax is available for matching arrays exactly [|x;y;z|].

It is important to understand that sequences (aka IEnumerables) can not be matched on this way directly, because they are "lazy" and meant to be accessed one element at a time. Lists and arrays, on the other hand, are fully available to be matched on.

Of these patterns, the most common one is the "cons" pattern, often used in conjunction with recursion to loop through the elements of the list.

Here are some examples of looping through lists using recursion:

The second example shows how we can carry state from one iteration of the loop to the next using a special "accumulator" parameter (called sumSoFar in this example). This is a very common pattern.

Matching on tuples, records and unions

Pattern matching is available for all the built-in F# types. More details in the series on types.

Matching the whole and the part with the "as" keyword

Sometimes you want to match the individual components of the value and also the whole thing. You can use the as keyword for this.

Matching on subtypes

You can match on subtypes, using the :? operator, which gives you a crude polymorphism:

This only works to find subclasses of a parent class (in this case, Object). The overall type of the expression has the parent class as input.

Note that in some cases, you may need to "box" the value.

The message tells you the problem: "runtime type tests are not allowed on some types". The answer is to "box" the value which forces it into a reference type, and then you can type check it:

In my opinion, matching and dispatching on types is a code smell, just as it is in object-oriented programming. It is occasionally necessary, but used carelessly is an indication of poor design.

In a good object oriented design, the correct approach would be to use polymorphism to replace the subtype tests, along with techniques such as double dispatch. So if you are doing this kind of OO in F#, you should probably use those same techniques.

Matching on multiple values

All the patterns we've looked at so far do pattern matching on a single value. How can you do it for two or more?

The short answer is: you can't. Matches are only allowed on single values.

But wait a minute -- could we combine two values into a single tuple on the fly and match on that? Yes, we can!

And indeed, this trick will work whenever you want to match on a set of values -- just group them all into a single tuple.

Guards, or the "when" clause

Sometimes pattern matching is just not enough, as we saw in this example:

Pattern matching is based on patterns only -- it can't use functions or other kinds of conditional tests.

But there is a way to do the equality test as part of the pattern match -- using an additional when clause to the left of the function arrow. These clauses are known as "guards".

Here's the same logic written using a guard instead:

This is nicer, because we have integrated the test into the pattern proper, rather than using a test after the match has been done.

Guards can be used for all sorts of things that pure patterns can't be used for, such as:

  • comparing the bound values

  • testing object properties

  • doing other kinds of matching, such as regular expressions

  • conditionals derived from functions

Let's look at some examples of these:

Using active patterns instead of guards

Guards are great for one-off matches. But if there are certain guards that you use over and over, consider using active patterns instead.

For example, the email example above could be rewritten as follows:

You can see other examples of active patterns in a previous post.

The "function" keyword

In the examples so far, we've seen a lot of this:

In the special case of function definitions we can simplify this dramatically by using the function keyword.

As you can see, the aValue parameter has completely disappeared, along with the match..with.

This keyword is not the same as the fun keyword for standard lambdas, rather it combines fun and match..with in a single step.

The function keyword works anywhere a function definition or lambda can be used, such as nested matches:

or lambdas passed to a higher order function:

A minor drawback of function compared with match..with is that you can't see the original input value and have to rely on value bindings in the pattern.

Exception handling with try..with

In the previous post, we looked at catching exceptions with the try..with expression.

The try..with expression implements pattern matching in the same way as match..with.

So in the above example we see the use of matching on a custom pattern

  • | Failure msg is an example of matching on (what looks like) an active pattern

  • | :? System.InvalidOperationException as ex is an example of matching on the subtype (with the use of as as well).

Because the try..with expression implements full pattern matching, we can also use guards as well, if needed to add extra conditional logic:

Wrapping match expressions with functions

Match expressions are very useful, but can lead to complex code if not used carefully.

The main problem is that match expressions doesn't compose very well. That is, it is hard to chain match..with expressions and build simple ones into complex ones.

The best way of avoiding this is to wrap match..with expressions into functions, which can then be composed nicely.

Here's a simple example. The match x with 42 is wrapped in a isAnswerToEverything function.

Library functions to replace explicit matching

Most built-in F# types have such functions already available.

For example, instead of using recursion to loop through lists, you should try to use the functions in the List module, which will do almost everything you need.

In particular, the function we wrote earlier:

can be rewritten using the List module in at least three different ways!

Similarly, the Option type (discussed at length in this post) has an associated Option module with many useful functions.

For example, a function that does a match on Some vs None can be replaced with Option.map:

Creating "fold" functions to hide matching logic

Finally, if you create your own types which need to be frequently matched, it is good practice to create a corresponding generic "fold" function that wraps it nicely.

For example, here is a type for defining temperature.

Chances are, we will matching these cases a lot, so let's create a generic function that will do the matching for us.

All fold functions follow this same general pattern:

Now we have our fold function, we can use it in different contexts.

Let's start by testing for a fever. We need a function for testing degrees F for fever and another one for testing degrees C for fever.

And then we combine them both using the fold function.

And now we can test.

For a completely different use, let's write a temperature conversion utility.

Again we start by writing the functions for each case, and then combine them.

Note that the conversion functions wrap the converted values in a new TemperatureType, so the convert function has the signature:

And now we can test.

We can even call convert twice in a row, and we should get back the same temperature that we started with!

There will be much more discussion on folds in the upcoming series on recursion and recursive types.

Last updated

Was this helpful?