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: case EquilateralTriangle: case Triangle: case Rectangle: case Pentagon:
|
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 |
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);; > shapeArea (Square, 5.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;; |
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

Join the conversation! Your thoughts help the community grow.