Understanding Pattern Matching in F#


Introduction

Pattern matching in F# is similar to using a switch statement in C#, but has a lot more flexibility.  You still need to be type safe, as you do in C#, but you really have a lot of flexibility in each "case" of the pattern.  The best way to understand pattern matching in F# is to take a set of examples. So let's begin.

Creating an Enumeration in F#

To create an enumeration in F#, you use the structure below.  It seems the only restriction was in the naming in that each enumerated value needs to be capitalized  otherwise the compiler complains).

Listing 1 - Shape Enumeration in F#

type shape = Circle | Square | Triangle | EquilateralTriangle | Rectangle | Pentagon;;

The interactive F# screen verifies that this is correct by listing the enumeration after hitting enter:

Listing 2 - Output from Interactive Window of Shape Enumeration

type shape =
| Circle
| Square
| Triangle
| EquilateralTriangle
| Rectangle
| Pentagon

We will use this enumeration in most of our examples to illustrate the flexibility of pattern matching, and how we can give functional meaning to all of the enumerated types through pattern matching:

Overview of Pattern Matching

In essence, pattern matching matches a value against a set of values or patterns of the same type.   Upon getting a correct match to the pattern, we can act upon it by executing a function, returning a value, or failing with an exception.  Our first example is pretty straightforward. We want to create a function called numberOfSides that takes a shape and matches it against all of the possible shapes.  Upon matching the correct shape, the function will return an integer representing the number of sides of the shape.

Listing 3 - Number of  Sides Patttern Matching function in F#

let numberOfSides(aShape:shape) =
match (aShape) with
| Circle -> 0
| Square -> 4
| EquilateralTriangle -> 3
| Triangle -> 3
| Rectangle -> 4
| Pentagon -> 5;;

We could imagine a similar switch statement in C# as shown below.  Note how much less work it is to write the same exact code in F#.  Also note that you do not require a default statement in F#, but if you don't include every possible case inside your enumeration, the statement will throw a compile error.

Listing 4 - Number of  Sides Equivalent Switch Statement in C#

switch (aShape)
{
  case Circle:
      return 0;
   break;

  case Square:
      return 4;
   break;

  case EquilateralTriangle:
      return 3;
   break;

  case Triangle:
      return 3;
   break;

  case Rectangle:
      return 4;
   break;

  case Pentagon:
      return 5;
   break;

  default: return 0;
}

 

When we run the F# code in Listing 3 on a Triangle, it does in fact return the value 3 as shown in the results below:

Listing 5 - Calling the numberOfSides function in F#

> numberOfSides Triangle;;

val it : int = 3

As long as you match against the correct type, you can return just about anything.  In the sample in listing 6 we create the shapeColor function which returns a color associated with each shape.  In this example we introduced a new pattern to match against, the underscore (_).  The underscore is like the default in a switch statement, but it basically says: "Match against anything else".  Our F# code will assign the color black to all other shapes that are not Circle, Square, or Triangle:

Listing 6 - Assigning Colors to Shapes through pattern matching in F#

let shapeColor(aShape:shape) =
match (aShape) with
| Circle -> "red"
| Square -> "green"
| Triangle -> "purple"
 | _ -> "black";;

The results of calling the shapeColor on the Circle is shown in listing 7 and does indeed return red.

Listing 7 - Calling the shapeColor method on a Circle  in F#

> shapeColor Circle;;
val it : string = "red"

If we try one of the other shapes not included in the pattern list we get a string of "black" returned:

Listing 8 - Calling the shapeColor method on a Shape for the default case

> shapeColor EquilateralTriangle;;
val it : string = "black"

For people doing graphics code in F#, it might be fairly useless returning a color represented by a string.  Why not return a real-live .NET Color? Any .NET class can be used in F# simply by calling open on the namespace you wish to use.  In the F# pattern matching code shown in listing 9, we return a System.Drawing color for each shape in our enumeration.

Listing 9 - Pattern matching a shape to a .NET Color object

open System.Drawing;
let shapeColor(aShape:shape) = match (aShape) with
| Circle -> Color.Red
| Square -> Color.Green
| Triangle -> Color.Purple | _ -> Color.Black;;

The results of calling shapeColor on a Circle gives us the Color object represented in F# (which looks a little different than it's C# counterpart, but it's just as complete):

Listing 10 - Results of calling the alternate shapeColor function on a Circle

> shapeColor Circle;;
val it : Color = Color [Red] {
           A = 255uy;
           B = 0uy;
           G = 0uy;
           IsEmpty = false;
           IsKnownColor = true;
           IsNamedColor = true;
           IsSystemColor = false;
           Name = "Red";
           R = 255uy;}

Using Functions in Pattern Matching

The power of F# is that it is a functional language and functions can be stuck all over the place in your code.  You can do the same in C#, but the amount of typing in F# is a lot less. (reminds me a little bit of my C coding days).  Some might argue that the "short-cut feel" of F#  is dangerous, poor design, hard to follow, and a few additional expletives, but the truth is that F# is a type-safe object-oriented language(yup, it has classes and interfaces). It's up to the user to write his or her F# code so people can actually read it.  Anyway, let's see how we can react to our patterns differently, but adding functions inside.  In the sample in listing 11, we want to associate an area calculation with each shape. The pattern matching statement looks almost exactly the same, only in this case we have passed an additional parameter, side into our shapeArea function.  We can actually use the parameter inside each of our pattern matching statements to perform the area calculation.

Listing 11 - pattern matching shapes to functions to compute their area

open System;

 let shapeArea(aShape:shape, side) = match (aShape) with
| Circle -> Math.PI * side * side
| Square -> side * side
| EquilateralTriangle -> side * side * Math.Sqrt(3.0)/4.0
| _ -> side * side;;

One might argue, that what I just did was completely not object-oriented.  In an object-oriented world, You would probably create a base class called Shape and several subclasses called Circle, Square, Triangle, EquilateralTriangle, etc.  You would then create a virtual function CalculateArea and override this function in each of your shape classes.  Or, alternatively,  you might create an Interface IShape, and implement the CalculateArea function in all the different shapes (Circle, Square, Triangle, etc.).   Note that you could do this in F# the way I just described,  but one might also argue that the functional model here is faster, maintainable, and type-safe, and a lot less code.  However, (and this is a big however), the code above is a completely different design paradigm, and should be approached differently.  My gut feel is that code for large projects should be a mixture of C# or more object-oriented F# and include the rapidly-coded functional F#.  But the functional F# should be wrapped very understandably in an API of some sort to black-box some of the uglier functional code that could arise from this type of coding.  Also, it's always a judgment call as to when you are writing functions that can be used by classes at a higher level, or when you are writing functions that step all over your software architecture.

Listing 12 shows the results of our area calculation for a side(or radius) of 5 for a few different shapes:

Listing 12 - Results of calling the shapeArea pattern matching function on different shapes

> shapeArea (Circle, 5.0);;
val it : float = 78.53981634

>shapeArea (EquilateralTriangle, 5.0);;
val it : float = 10.82531755

> shapeArea (Square, 5.0);;
val it : float = 25.0

(Square, 5.0) |> shapeArea;;
val it : float = 25.0

Notice that our default pattern just computes the area as side * side, but this is in fact incorrect.  For a triangle with different length sides, we cannot compute the area by just knowing one side of the shape.   The same is true for a rectangle.  There is no way of knowing the area from one side of the rectangle.  In order to correct the potential error, we can insert a different function in our default case called failwith.  This will force the code to throw an exception for all shapes that don't have enough information to do the area calculation.

Listing 13 - Adding default behavior to the shapArea function

> let shapeArea(aShape:shape, side) = match (aShape) with
| Circle -> Math.PI * side * side
| Square -> side * side
| EquilateralTriangle -> side * side * Math.Sqrt(3.0)/4.0
| _ -> failwith ("not enough info to calculate area of shape");;

Using Pattern matching like a set of "if statements"

Because pattern matching in F# can return any type, you can also return a boolean type.  In listing 14 we create a function called hasFourSides that returns true if the shape has four sides.  Since only rectangles and squares have four sides, we return true for these shapes, and false for everything else.

Listing 14 - Boolean pattern matching function hasFourSides

let hasFourSides (aShape:shape) =
match (aShape) with
| Square -> true
| Rectangle -> true
| _ -> false;;

The results of running the hasFourSides function on a Circle and a Square  is shown in the results below:

Listing 14 - Results of calling the pattern matching function hasFourSides

hasFourSides Circle;;
val it : bool = false

hasFourSides Square;;
val it : bool = true

Type Safety in Pattern Matching

If we try to run the hasFourSides function on something that is not a shape, we will get an exception.  In the first case shown in listing 15, we just invent some object name, and the compiler tells us googoo has no constructor.

Listing 15 - Results of calling the pattern matching function hasFourSides on an undefined type

> hasFourSides googoo;;
hasFourSides googoo
-------------^^^^^^^  stdin(228,13): error: FS0039: The value or constructor 'googoo' is not defined. stopped due to error

If we try to run hasFourSides on a known type (such as an integer) that is not a shape, we get a type exception as shown in listing 16:

Listing 16 - Results of calling the pattern matching function hasFourSides on a type that is not a shape

>hasFourSides 5;; 

hasFourSides 5;;
-------------^^
stdin(230,13): error: FS0001: This expression has type
int  but is here used with type  shape  stopped due to error

Using Conditions in a Pattern

Here is where F# pattern matching shows a bit more of it's muscle than a C# switch statement.  You can use a when clause in your pattern which will match against a particular condition for the type you are matching against.  In our example in listing 17, we want to find all shapes with less than four sides.  We could list those shapes explicitly, but it is faster to use the numberOfSides function that we have already created and compare the result to see if it is less than 4 in a when clause.  If it is less than 4, we can return true, otherwise we return false. 

Listing 17 - Adding a condition to a pattern that checks for shapes with sides less than 4

let lessThanFourSides (aShape:shape) =
match (aShape) with
x when numberOfSides(aShape) < 4 -> true
| _ -> false;;

// Results of testing a triangle and a square

> lessThanFourSides Triangle;;
val it : bool = true

> lessThanFourSides Square;;
val it : bool = false

 

Note that we need to specifiy a variable in our when clause called x.  I'm not exactly sure what one needs the x variable name, but F# requires it.  If we look at the code produced by F# using Reflector (listing 18), we see that x doesn't really do anything other than hold the shape parameter.  It is unnecessary in the actual code here.  I suppose if you needed to use x inside of a method belonging to your pattern, it could prove useful.

Listing 18 - Results of viewing the F# hasFourSides function in Reflector

public static bool lessThanFourSides(shape aShape)
{
Test.shape shape = aShape;
Test.shape x = shape;
if (numberOfSides(aShape) < 4)
{
Test.shape x = shape;
return true;
}
return false;
}

Patterns in Lists

Another powerful pattern matching feature is matching patterns in lists.  You can perform different actions on a list, based on what particular pattern you are looking for in the list.  FSharp has it's own list collections, and our example in listing 19 shows how to match against the List collection.  In the code, we created a simple list that has the numbers 1,2,3,4,5 and we are looking to see if the list starts with 1.  If it matches the pattern 1::_, then it starts with 1.  The :: operator is called the Cons operator which I think this is short for consecutive, but I'm not sure. (At least I'm sure it's not short for Constant). This operator is used to match the pattern of items adjacent to each other in the list.

Listing 19 - Matching the first number in a list to 1

open Microsoft.FSharp.Collections;
let
aList = [1;2;3;4;5];;

let
ListStartsWithOne(myList) = match(myList) with
| 1::_
-> printf "starts with 1\n"
| []
-> printf "empty\n"
| _ ::_
-> printf "doesn't start with 1\n";;

You can also manipulate lists, by replacing the underscore (_) with a variable.  For example if we wanted to print out all the numbers in the list after 1, we could provide the following pattern matching statements shown in listing 20:

Listing 20 - Retrieving parts of a pattern in a List

let printOtherValuesAfterOne(myList) = match(myList) with
| 1::x
-> print_any (x)
| []
-> printf "\nempty\n"
| _::_
-> printf "\ndoesn't start with 1\n";;

printOtherValuesAfterOne (aList);;

Listing 20 produces the result [2; 3; 4; 5] for the variable x for the parent list [1;2;3;4;5].    [1;2;3;4;5] matches the pattern 1:x, and x = [2;3;4;5] in this case.  A pattern matching statement returns a copy of the list it is operating on.  As a result, you can do some pretty funky things with pattern matching, recursion and lists.  Say we have a list of floats  [1.0; 2.0; 3.0; 4.0; 5.0]  and we want to produce a list that is exactly half of every number in the list.  We can write a pattern matching statement like the one in listing 21.  This recursive function HalveList goes through each head element and divides it by two.  It then calls HalveList on the remaining tail elements.  It will continue to do the recursive call until the tail list is empty, matching the pattern [].  The resulting output to the console is  [0.5; 1.0; 1.5; 2.0; 2.5].

Listing 21 - Halving all the Elements in a List

let myList = [1.0;2.0;3.0;4.0;5.0];;

let rec HalveList aList =
match
aList with
  | head::tail
-> head/2.0 :: HalveList(tail)
  | []
-> [];;

let listHalved = HalveList myList;;

print_any (listHalved);;

What if we wanted to take the product of all elements in the list? We could do something very similar.  If the pattern matches the head element followed by all the consecutive elements, we can multiply the head element by the product of all the rest of the tail elements recursively.  When the tail list is empty, we simply multiply by one.  The result for a list [1.0; 2.0; 3.0; 4.0; 5.0]  is 120.0.

Listing 22 - Getting the Product of the Elements in a List

let rec CalcProductList aList =
match
aList with
   |  head::tail
-> head * CalcProductList(tail)
   | []
-> 1.0;;

let product = CalcProductList(myList);;

print_any (product);;

Let's say we only wanted the product of the even numbers in the list?  We can use a condition on the head of the list to make sure it is an even number(the condition being (int)head % 2 = 0).  Remember that like switch statements, we need to handle all the cases for a pattern match statement.  Therefore, we also need to handle the odd number cases by re-calling the recursive function without calculating a product. This is handled by the following pattern | head::tail -> CalcProductList(tail).  We also still need to handle the empty list condition for when the tail list is empty.  This is handled with the  | [] -> 1.0 pattern which multiplies our product by one and doesn't affect the result.

Listing 23 - Getting the Product of the Even Elements in a List

let rec CalcProductList aList =
match
aList with
   | head::tail
when (int)head % 2 = 0 -> head * CalcProductList(tail)
   |
head::tail -> CalcProductList(tail);
   | []
-> 1.0;;

Extracting a list of fields from a list of records

One more thing I found useful with pattern matching of lists is to extract a list of field values from within a list of F# records.  Let's say we have a list of customers and we want to extract all the phone numbers of those customers from the list.  We can use pattern matching to get the desired list of phone numbers.  We just recursively append the phone value of the customer (the head value) to a new list of phone numbers using pattern matching.

Listing 24 - Extracting a list of phone numbers from a list of Customer objects

// F# Customer Record
type Customer =
{
 
name:string;
 
phone:string;
}

// List of Customers
let
myCustomers = [ {name = "harry"; phone = "555-123-2345"};
                                    {
name = "bill"; phone = "555-223-8342"};
                                    {
name = "fred"; phone = "212-555-8342"};];;

//  pattern matching to get list of phone numbers recursively
let
rec phoneNumbers(customers) =
 match (customers) with
   |
head::tail -> head.phone :: phoneNumbers(tail)
   | [] -> [];;

// print out the list of phone numbers
print_any
(phoneNumbers myCustomers);;

Now let's just filter the list and get a list of phone numbers with New York Area Codes.  Here we recursively append numbers that start with 212 using the when clause.  Again we need to handle all possible cases, so cases where the number does not start with 212, we call NewYorkNumbers recursively with the tail.

Listing 25 - Extracting a list of NY phone numbers from a list of Customer objects

//  pattern matching to get list of New York phone numbers recursively

let rec NewYorkNumbers(customers) =
  
match (customers) with
    |
head::tail when head.phone.StartsWith("212") = true -> head.phone :: NewYorkNumbers(tail)
    |
head::tail -> NewYorkNumbers(tail)
    | []
-> [];;

// print out the list of NY phone numbers
print_any
(NewYorkNumbers myCustomers);;

Note that a lot of what I've shown here can be done a lot easier using LINQ, but the code samples do illustrate some of the power of pattern matching with a List collection.

Conclusion

Pattern matching gives you a powerful way to perform decision making based on the aspects of a particular F# type.  It is in some ways similar to a C# switch statement, but gives you a lot more functionality and flexibility.  Not only can you match patterns in your objects, enumerations, and lists, but you can filter on these patterns for particular conditions.  As a result of your F# type matching a pattern, you can perform an action on that type.  Another compelling use of pattern matching is to use patterns to match items in a list.  In turn, you can use the items in one list to create a new list based on the pattern matched.  You can also perform aggregate functions on the items in the list.  Anyway, have fun experimenting with F# in the interactive window or in your own F# project file.  I think you'll find that it may provide you with a few hours of programming F#un.