Introduction to Functional Programming in F# – Part 7
Introduction
Welcome to the seventh post in this introductory series on functional programming in F#. In this post we will be extending our knowledge of Pattern Matching by looking at how we can write our own matchers with Active Patterns. There are a number of different Active Patterns available but we will be concentrating on Partial & Multi-Case in this post.
Setting Up
All of the code can be written in F# script files (.fsx) and run using F# Interactive.
Partial Active Patterns
We are going to start with an old Interview favourite - FizzBuzz. Let's have a go at a simple solution;
It works but can we do better with Pattern Matching? How about this?
or this?
None of these is very readable. How about if we could do something like this?
We can use a Partial Active Pattern to do this:
The (|..|) are colloquially called 'banana clips'. Note the single '&' to apply both parts of the pattern match in the calculate function.
This is Ok but what happens if we need to add 7 -> Bazz into the mix? Look at the code now!
Maybe not such a good idea? How confident would you be that you had covered all of the permutations? Maybe a different approach would yield a better result?
This is much nicer. The fold function will concatenate all of the strings generated in the map to the original empty string.
To run the function we can use:
Let's have a look at another Interview question - Leap Years. The basic F# function looks like this:
But can Partial Active Pattern help make it more readable?
There's more code but you could easily argue that it is more readable? We could also do a similar thing to the original with helper functions rather than Active Patterns;
All three approaches are valid and it's down to preference which is best.
One area where Partial Active Patterns are really helpful is in validation and parsing. This is an example of parsing a string to a DateTime:
The only difference here is that we are returning the parsed value from the Partial Active Pattern instead of unit.
Multi-Case Active Patterns
Multi-Case Active Patterns are different from Partial Active Patterns in that they allow more than one choice and return the choice rather than an option type. The maximum number of choices supported is currently seven.
I play (rather unsuccessfully) in an online Football (the sport where participants kick a spherical ball with their feet rather than throw an egg-shaped object with their hands) Score Predictor.
The rules are simple;
300 points for predicting the correct score (2-3 vs 2-3)
100 points for predicting the correct result (2-3 vs 0-2)
15 points per home goal & 20 points per away goal using the lower of the predicted and actual scores
We have some sample predictions, actual scores and points that we can use to validate the code we write;
Firstly we create a simple tuple type to represent a score;
To determine if we have a correct score, we need to check that the prediction and the actual score are the same. Since F# uses structural equality rather than reference equality, this is trivial with a partial active pattern;
We also need to determine what the result of a Score is. This can only one of three choices; draw, home win or away win. We can easily represent this with a multi-case active pattern;
Note that this active pattern returns one of the pattern choices rather than an option type. We can now create a new partial active pattern for determining if we have predicted the correct result;
Without the multi-case active pattern, we would have to write something like this;
I prefer the version using the Multi-Case Active Pattern. Now we need to create a function to work out the points for the goals scored;
We now have all of the parts to create our function to calculate the total points for each game;
There is a bit of duplication/similarity that we will resolve later but firstly we should use our test data to validate our code;
We can simplify our goalsScore fumction using some built in functionality of a tuple - fst and snd;
We can also simplify the calculatePoints functions by combining the pattern matching for CorrectScore and CorrectResult into a new function;
Note that we had to return 400 from CorrectScore in this function as we are no longer able to add the CorrectResult points later. This allows us to simplify the calculatePoints function;
We can use a Higher Order function to remove the duplication;
List.sumBy is equivalent to List.map followed by List.sum.
There are other types of Active Pattern that we haven't met in this post; I recommend that you investigate the F# Documentation to make yourself familiar with them.
Conclusion
Active Patterns are very useful (and powerful) features but they are not always the best approach.
In the next post we will be taking some of the features we have met in this post to add validation to the code we used in Post 6.
If you have any comments on this series of posts or suggestions for new ones, send me a tweet (@ijrussell) and let me know.