layout: post
title: “Match expressions”
description: “The workhorse of F#”
nav: thinking-functionally
seriesId: “Expressions and syntax”
seriesOrder: 9

categories: [Patterns,Folds]

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:

  1. match [something] with
  2. | pattern1 -> expression1
  3. | pattern2 -> expression2
  4. | pattern3 -> expression3

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

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

Where each lambda expression has exactly one parameter:

  1. 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.

  1. _ -> expression

Order is important!

Looking at the following example:

  1. let x =
  2. match 1 with
  3. | 1 -> "a"
  4. | 2 -> "b"
  5. | _ -> "z"

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

  1. fun 1 -> "a"
  2. fun 2 -> "b"
  3. fun _ -> "z"

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:

  1. let x =
  2. match 1 with
  3. | _ -> "z"
  4. | 1 -> "a"
  5. | 2 -> "b"

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.

  1. let f x = match x with
  2. // aligned
  3. | 1 -> "pattern 1"
  4. // aligned
  5. | 2 -> "pattern 2"
  6. // aligned
  7. | _ -> "anything"

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:

  1. // ugly alignment!
  2. let myVeryLongNameForAFunction myParameter = match myParameter with
  3. | 1 -> "something"
  4. | _ -> "anything"
  5. // much better
  6. let myVeryLongNameForAFunction myParameter =
  7. match myParameter with
  8. | 1 -> "something"
  9. | _ -> "anything"

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.

  1. let f x =
  2. match x with
  3. | "a very long pattern that breaks up the flow" -> "something"
  4. | _ -> "anything"
  5. let f x =
  6. match x with
  7. | "a very long pattern that breaks up the flow" ->
  8. "something"
  9. | _ ->
  10. "anything"

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

  1. let f list =
  2. match list with
  3. | [] -> "something"
  4. | x::xs -> "something else"

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.

  1. let x =
  2. match 1 with
  3. | 1 -> 42
  4. | 2 -> true // error wrong type
  5. | _ -> "hello" // error wrong type

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:

  1. // nested match..withs are ok
  2. let f aValue =
  3. match aValue with
  4. | x ->
  5. match x with
  6. | _ -> "something"

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

  1. [2..10]
  2. |> List.map (fun i ->
  3. match i with
  4. | 2 | 3 | 5 | 7 -> sprintf "%i is prime" i
  5. | _ -> sprintf "%i is not prime" i
  6. )

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:

  1. let x =
  2. match 42 with
  3. | 1 -> "a"
  4. | 2 -> "b"

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:

  1. let x =
  2. match 42 with
  3. | 1 -> "a"
  4. | 2 -> "b"
  5. | _ -> "z"

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:

  1. type Choices = A | B | C
  2. let x =
  3. match A with
  4. | A -> "a"
  5. | B -> "b"
  6. | C -> "c"
  7. //NO default match

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.

  1. let x =
  2. match -1 with
  3. | 1 -> "a"
  4. | 2 -> "b"
  5. | i when i >= 0 && i<=100 -> "ok"
  6. // the last case will always match
  7. | x -> failwithf "%i is out of range" x

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:

  1. let y =
  2. match (1,0) with
  3. // binding to a named value
  4. | (1,x) -> printfn "x=%A" x

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:

  1. let elementsAreEqual aTuple =
  2. match aTuple with
  3. | (x,x) ->
  4. printfn "both parts are the same"
  5. | (_,_) ->
  6. printfn "both parts are different"

Instead, you have to do something like this:

  1. let elementsAreEqual aTuple =
  2. match aTuple with
  3. | (x,y) ->
  4. if (x=y) then printfn "both parts are the same"
  5. else printfn "both parts are different"

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:

  1. let y =
  2. match (1,0) with
  3. // OR -- same as multiple cases on one line
  4. | (2,x) | (3,x) | (4,x) -> printfn "x=%A" x
  5. // AND -- must match both patterns at once
  6. // Note only a single "&" is used
  7. | (2,x) & (_,1) -> printfn "x=%A" x

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

  1. type Choices = A | B | C | D
  2. let x =
  3. match A with
  4. | A | B | C -> "a or b or c"
  5. | D -> "d"

Matching on lists

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

  1. let y =
  2. match [1;2;3] with
  3. // binding to explicit positions
  4. // square brackets used!
  5. | [1;x;y] -> printfn "x=%A y=%A" x y
  6. // binding to head::tail.
  7. // no square brackets used!
  8. | 1::tail -> printfn "tail=%A" tail
  9. // empty list
  10. | [] -> printfn "empty"

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:

  1. // loop through a list and print the values
  2. let rec loopAndPrint aList =
  3. match aList with
  4. // empty list means we're done.
  5. | [] ->
  6. printfn "empty"
  7. // binding to head::tail.
  8. | x::xs ->
  9. printfn "element=%A," x
  10. // do all over again with the
  11. // rest of the list
  12. loopAndPrint xs
  13. //test
  14. loopAndPrint [1..5]
  15. // ------------------------
  16. // loop through a list and sum the values
  17. let rec loopAndSum aList sumSoFar =
  18. match aList with
  19. // empty list means we're done.
  20. | [] ->
  21. sumSoFar
  22. // binding to head::tail.
  23. | x::xs ->
  24. let newSumSoFar = sumSoFar + x
  25. // do all over again with the
  26. // rest of the list and the new sum
  27. loopAndSum xs newSumSoFar
  28. //test
  29. loopAndSum [1..5] 0

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.

  1. // -----------------------
  2. // Tuple pattern matching
  3. let aTuple = (1,2)
  4. match aTuple with
  5. | (1,_) -> printfn "first part is 1"
  6. | (_,2) -> printfn "second part is 2"
  7. // -----------------------
  8. // Record pattern matching
  9. type Person = {First:string; Last:string}
  10. let person = {First="john"; Last="doe"}
  11. match person with
  12. | {First="john"} -> printfn "Matched John"
  13. | _ -> printfn "Not John"
  14. // -----------------------
  15. // Union pattern matching
  16. type IntOrBool= I of int | B of bool
  17. let intOrBool = I 42
  18. match intOrBool with
  19. | I i -> printfn "Int=%i" i
  20. | B b -> printfn "Bool=%b" b

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.

  1. let y =
  2. match (1,0) with
  3. // binding to three values
  4. | (x,y) as t ->
  5. printfn "x=%A and y=%A" x y
  6. printfn "The whole tuple is %A" t

Matching on subtypes

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

  1. let x = new Object()
  2. let y =
  3. match x with
  4. | :? System.Int32 ->
  5. printfn "matched an int"
  6. | :? System.DateTime ->
  7. printfn "matched a datetime"
  8. | _ ->
  9. printfn "another type"

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.

  1. let detectType v =
  2. match v with
  3. | :? int -> printfn "this is an int"
  4. | _ -> printfn "something else"
  5. // error FS0008: This runtime coercion or type test from type 'a to int
  6. // involves an indeterminate type based on information prior to this program point.
  7. // Runtime type tests are not allowed on some types. Further type annotations are needed.

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:

  1. let detectTypeBoxed v =
  2. match box v with // used "box v"
  3. | :? int -> printfn "this is an int"
  4. | _ -> printfn "something else"
  5. //test
  6. detectTypeBoxed 1
  7. detectTypeBoxed 3.14

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!

  1. let matchOnTwoParameters x y =
  2. match (x,y) with
  3. | (1,y) ->
  4. printfn "x=1 and y=%A" y
  5. | (x,1) ->
  6. printfn "x=%A and y=1" x

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

  1. let matchOnTwoTuples x y =
  2. match (x,y) with
  3. | (1,_),(1,_) -> "both start with 1"
  4. | (_,2),(_,2) -> "both end with 2"
  5. | _ -> "something else"
  6. // test
  7. matchOnTwoTuples (1,3) (1,2)
  8. matchOnTwoTuples (3,2) (1,2)

Guards, or the “when” clause

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

  1. let elementsAreEqual aTuple =
  2. match aTuple with
  3. | (x,y) ->
  4. if (x=y) then printfn "both parts are the same"
  5. else printfn "both parts are different"

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:

  1. let elementsAreEqual aTuple =
  2. match aTuple with
  3. | (x,y) when x=y ->
  4. printfn "both parts are the same"
  5. | _ ->
  6. printfn "both parts are different"

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:

  1. // --------------------------------
  2. // comparing values in a when clause
  3. let makeOrdered aTuple =
  4. match aTuple with
  5. // swap if x is bigger than y
  6. | (x,y) when x > y -> (y,x)
  7. // otherwise leave alone
  8. | _ -> aTuple
  9. //test
  10. makeOrdered (1,2)
  11. makeOrdered (2,1)
  12. // --------------------------------
  13. // testing properties in a when clause
  14. let isAM aDate =
  15. match aDate:System.DateTime with
  16. | x when x.Hour <= 12->
  17. printfn "AM"
  18. // otherwise leave alone
  19. | _ ->
  20. printfn "PM"
  21. //test
  22. isAM System.DateTime.Now
  23. // --------------------------------
  24. // pattern matching using regular expressions
  25. open System.Text.RegularExpressions
  26. let classifyString aString =
  27. match aString with
  28. | x when Regex.Match(x,@".+@.+").Success->
  29. printfn "%s is an email" aString
  30. // otherwise leave alone
  31. | _ ->
  32. printfn "%s is something else" aString
  33. //test
  34. classifyString "alice@example.com"
  35. classifyString "google.com"
  36. // --------------------------------
  37. // pattern matching using arbitrary conditionals
  38. let fizzBuzz x =
  39. match x with
  40. | i when i % 15 = 0 ->
  41. printfn "fizzbuzz"
  42. | i when i % 3 = 0 ->
  43. printfn "fizz"
  44. | i when i % 5 = 0 ->
  45. printfn "buzz"
  46. | i ->
  47. printfn "%i" i
  48. //test
  49. [1..30] |> List.iter fizzBuzz

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:

  1. open System.Text.RegularExpressions
  2. // create an active pattern to match an email address
  3. let (|EmailAddress|_|) input =
  4. let m = Regex.Match(input,@".+@.+")
  5. if (m.Success) then Some input else None
  6. // use the active pattern in the match
  7. let classifyString aString =
  8. match aString with
  9. | EmailAddress x ->
  10. printfn "%s is an email" x
  11. // otherwise leave alone
  12. | _ ->
  13. printfn "%s is something else" aString
  14. //test
  15. classifyString "alice@example.com"
  16. classifyString "google.com"

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:

  1. let f aValue =
  2. match aValue with
  3. | _ -> "something"

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

  1. let f =
  2. function
  3. | _ -> "something"

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:

  1. // using match..with
  2. let f aValue =
  3. match aValue with
  4. | x ->
  5. match x with
  6. | _ -> "something"
  7. // using function keyword
  8. let f =
  9. function
  10. | x ->
  11. function
  12. | _ -> "something"

or lambdas passed to a higher order function:

  1. // using match..with
  2. [2..10] |> List.map (fun i ->
  3. match i with
  4. | 2 | 3 | 5 | 7 -> sprintf "%i is prime" i
  5. | _ -> sprintf "%i is not prime" i
  6. )
  7. // using function keyword
  8. [2..10] |> List.map (function
  9. | 2 | 3 | 5 | 7 -> sprintf "prime"
  10. | _ -> sprintf "not prime"
  11. )

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.

  1. try
  2. failwith "fail"
  3. with
  4. | Failure msg -> "caught: " + msg
  5. | :? System.InvalidOperationException as ex -> "unexpected"

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:

  1. let debugMode = false
  2. try
  3. failwith "fail"
  4. with
  5. | Failure msg when debugMode ->
  6. reraise()
  7. | Failure msg when not debugMode ->
  8. printfn "silently logged in production: %s" msg

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.

  1. let times6 x = x * 6
  2. let isAnswerToEverything x =
  3. match x with
  4. | 42 -> (x,true)
  5. | _ -> (x,false)
  6. // the function can be used for chaining or composition
  7. [1..10] |> List.map (times6 >> isAnswerToEverything)

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:

  1. let rec loopAndSum aList sumSoFar =
  2. match aList with
  3. | [] ->
  4. sumSoFar
  5. | x::xs ->
  6. let newSumSoFar = sumSoFar + x
  7. loopAndSum xs newSumSoFar

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

  1. // simplest
  2. let loopAndSum1 aList = List.sum aList
  3. [1..10] |> loopAndSum1
  4. // reduce is very powerful
  5. let loopAndSum2 aList = List.reduce (+) aList
  6. [1..10] |> loopAndSum2
  7. // fold is most powerful of all
  8. let loopAndSum3 aList = List.fold (fun sum i -> sum+i) 0 aList
  9. [1..10] |> loopAndSum3

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:

  1. // unnecessary to implement this explicitly
  2. let addOneIfValid optionalInt =
  3. match optionalInt with
  4. | Some i -> Some (i + 1)
  5. | None -> None
  6. Some 42 |> addOneIfValid
  7. // much easier to use the built in function
  8. let addOneIfValid2 optionalInt =
  9. optionalInt |> Option.map (fun i->i+1)
  10. Some 42 |> addOneIfValid2

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.

  1. type TemperatureType = F of float | C of float

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

  1. module Temperature =
  2. let fold fahrenheitFunction celsiusFunction aTemp =
  3. match aTemp with
  4. | F f -> fahrenheitFunction f
  5. | C c -> celsiusFunction c

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.

  1. let fFever tempF =
  2. if tempF > 100.0 then "Fever!" else "OK"
  3. let cFever tempC =
  4. if tempC > 38.0 then "Fever!" else "OK"
  5. // combine using the fold
  6. let isFever aTemp = Temperature.fold fFever cFever aTemp

And now we can test.

  1. let normalTemp = C 37.0
  2. let result1 = isFever normalTemp
  3. let highTemp = F 103.1
  4. let result2 = isFever highTemp

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.

  1. let fConversion tempF =
  2. let convertedValue = (tempF - 32.0) / 1.8
  3. TemperatureType.C convertedValue //wrapped in type
  4. let cConversion tempC =
  5. let convertedValue = (tempC * 1.8) + 32.0
  6. TemperatureType.F convertedValue //wrapped in type
  7. // combine using the fold
  8. let convert aTemp = Temperature.fold fConversion cConversion aTemp

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

  1. val convert : TemperatureType -> TemperatureType

And now we can test.

  1. let c20 = C 20.0
  2. let resultInF = convert c20
  3. let f75 = F 75.0
  4. let resultInC = convert f75

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

  1. let resultInC = C 20.0 |> convert |> convert

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