So last time we looked at Arrays, this time we will look at another core F# technique called pattern matching. Patterns are rules for transforming input data. They are used throughout the F# language to compare data with a logical structure or structures, decompose data into constituent parts, or extract information from data in various ways.
We will now proceed to look at pattern matching various things we have seen so far, however as before MSDN also has good coverage on pattern matching since it is such an integral part of using F#. You can read more at MSDN right here : http://msdn.microsoft.com/en-us/library/dd547125.aspx
Just before we start I just want to walk through one example that you may see from time to time, that may confuse you. To understand it, we will be starting with some C# code. which is as follows:
public void EnumerableDemo()
{
Enumerable.Range(1, 10).Select(x => DoubleIt(x));
}
public int DoubleIt(int input)
{
return input*2;
}
This code is very simple, and it is really JUST the select projection that is of interest here. The code above is totally valid, where we use the Select extension method, and call the DoubleIt()
method, where we pass in the int input value and get an int value returned. There is however another way (those of you that have Resharper installed will no doubt be well aware of this, as Resharper is very proactive about warning users of the alternative) of doing this, which is as follows:
public void EnumerableDemo()
{
Enumerable.Range(1, 10).Select(DoubleIt);
}
public int DoubleIt(int input)
{
return input*2;
}
See how we were able to refine the syntax of the select extension method call. In C# this is called a method group. The reason I went into the C# code above is that F# also allows you to perform the same sort of trick (although nothing to do with LINQ/Select methods) when we do pattern matching, lets see a trivial example shall we:
In this code we express the pattern matching using the full syntax (match x with….)
let someFunction x =
match x with
| 10 -> printfn "it was 10"
| _ -> printfn "it was not 10"
do someFunction 10
do someFunction 12
Which works as expected (you may have to trust me on this one)
We can however also do this instead:
let someFunction = function
| 10 -> printfn "it was 10"
| _ -> printfn "it was not 10"
do someFunction 10
do someFunction 12
Where we have removed the input parameter all together, and now use the “function
” keyword.The “function
” keyword works anywhere a function definition or lambda can be used
Order
When using pattern matching the order of the matches is important, for example consider this code (the last match is known as a wild card match, which we will talk about in just a minute):
let someFunction x =
match x with
| 1 -> printfn "it was 1"
| 2 -> printfn "it was 2"
| _ -> printfn "it was something else"
do someFunction 1
do someFunction 2
do someFunction 3
Which when run, gives us what we want, as shown below:
But what happens if we put the wild card match first, so we had this code:
let someFunction x =
match x with
| _ -> printfn "it was something else"
| 1 -> printfn "it was 1"
| 2 -> printfn "it was 2"
do someFunction 1
do someFunction 2
do someFunction 3
This time we get the following output which is obviously wrong, which is due to the matching hitting the wild card match first for any input value
Exhaustive Matches
We will stick with the example we have been seeing so far. The only amendment I have made is that I have removed the wild card match, so we are now left with this code:
let someFunction x =
match x with
| 1 -> printfn "it was 1"
| 2 -> printfn "it was 2"
do someFunction 1
do someFunction 2
do someFunction 3
This code looks totally fine, and compiles just fine. However when you try and run this code, you will get a MatchFailureException thrown, due to the unmatched 3 value.
This is easily fixed we can just include a new match for the 3, which would give us this new code:
let someFunction x =
match x with
| 1 -> printfn "it was 1"
| 2 -> printfn "it was 2"
| 3 -> printfn "it was 3"
do someFunction 1
do someFunction 2
do someFunction 3
But then what happens when we need to match more than 1,2 or 3. Arghhh, Surely there is a better way. Luckily there is, it is called the “wild card match” which we will look at next.
Wild Card Pattern
The wild card match, will literally match anything, it is expressed using an underscore “_” and it can be used alone or as part of a composite pattern matching (such as maybe a tuple).
To keep things simple we will continue to work with our trivial example. So I have left the explicit matches in place for 1 and 2, but have also introduced a wild card match for anything else. So the revised code is as follows:
let someFunction x =
match x with
| 1 -> printfn "it was 1"
| 2 -> printfn "it was 2"
| _ -> printfn "it was somthing else"
do someFunction 1
do someFunction 2
do someFunction 3
do someFunction 4
do someFunction 5
The wildcard match is great, but one area when you would want to use exhaustive matching over the wild card is when matching discriminated unions, I would suggest you DO NOT use a wild card match there.
Variable Pattern
The variable pattern allow you to assign the values of the match to variable name, which you may use to the right of the -> symbol. Here is an example:
let someFunction x =
match x with
| (var1, var2) when var1 > var2 -> printfn "%d is greater than %d" var1 var2
| (var1, var2) when var1 < var2 -> printfn "%d is less than %d" var1 var2
| (var1, var2) -> printfn "%d equals %d" var1 var2
someFunction (1,2)
someFunction (2, 1)
someFunction (0, 0)
Which when run produces this:
When working with tuples this is VERY useful pattern, as it allows you to match on the different parts of the tuple
And Pattern
You can also use the AND operator “&” when pattern matching that would allow you to match one or more conditions. Here is a very simple example based on matching one or more int values contained within a tuple that is supplied to the function (I have borrowed this example from MSDN)
let detectZeroAND point =
match point with
| (0, 0) -> printfn "Both values zero."
| (var1, var2) & (0, _) -> printfn "First value is 0 in (%d, %d)" var1 var2
| (var1, var2) & (_, 0) -> printfn "Second value is 0 in (%d, %d)" var1 var2
| _ -> printfn "Both nonzero."
detectZeroAND (0, 0)
detectZeroAND (1, 0)
detectZeroAND (0, 10)
detectZeroAND (10, 15)
Which when run produces the following:
Or Pattern
You can also use the OR operator “|” when pattern matching that would allow you to match one or more conditions. Here is a very simple example based on matching one or more int values.
let orFunction x =
match x with
| 0 | 1 -> printfn "It was 0 or 1"
| 2 -> printfn "It was 2"
for x in 0..2 do orFunction x
Which when run produces this:
You can also apply this to things like Tuples, here is an example of that:
let detectZeroOR point =
match point with
| (0, 0) | (0, _) | (_, 0) -> printfn "Zero found."
| _ -> printfn "Both nonzero."
detectZeroOR (0, 0)
detectZeroOR (1, 0)
detectZeroOR (0, 10)
detectZeroOR (10, 15)
Which when run produces this:
Matching Constants
It is sometimes useful to keep certain constant values that hold special meaning, such as perhaps an int value who’s real intention is “FAILURE”, and it may be nice to pattern match again these constants. Here is an example of you you may do that, take not of the [<Literal>]
attribute that we applied to the int values, such that they could be treated as literal values in pattern matching and other places.
[<Literal>]
let FAILURE = 0
[<Literal>]
let UNKNOWN = 1
[<Literal>]
let SUCCESS = 2
let literalFunction x =
match x with
| FAILURE | UNKNOWN -> printfn "It did not work"
| SUCCESS -> printfn "It worked\r\n"
for x in 0..2 do literalFunction x
When run this produces this:
Matching Tuples
Matching tuples is an interesting one, as you can use the wild card for different parts of the tuple matching, or even the whole tuple. Here is example
let someFunction x =
match x with
| (1,1) -> printfn "it was (1,1)"
| (_,2)-> printfn "2nd part was 2"
| (3,_) -> printfn "1st part was 3"
| (_,_) -> printfn "its a freak"
do someFunction (1,1)
do someFunction (4,2)
do someFunction (3,5)
do someFunction (10,4)
Which when run produces the following results
Matching Options
Matching options is also very easy we just do the following sort of thing:
let someFunction x =
match x with
| Some(y) -> printfn "Some %A" y
| None-> printfn "None"
do someFunction (Option.Some(24))
do someFunction None
Which when run produces the following results
Matching Records
Matching records is also pretty simply to do, where you can match the individual field level parts of the record. For example here is some code that will match on the Name field of a Person type. You could of course loop in any of the record types fields in the pattern matching
type Person = { Name : string }
.....
.....
let someFunction x =
match x with
| { Name= "Fred"} -> printfn "Its Fred"
| { Name= "Sam"} -> printfn "Its Sam"
| _ -> printfn "Its not Fred or Sam"
do someFunction { Name = "Sam"}
do someFunction { Name = "Fred"}
do someFunction { Name = "Daniel"}
Which when run will give the following results
Matching Discriminated Unions : Empty cases
There are 2 flavours of unions that we can match against, the empty union cases (much like enums in .NET),which we can match as follows:
type SupportedAnimals =
| Cat
| Dog
| Spider
| Snake
.......
.......
let someFunction x =
match x with
| Cat -> printfn "Its a Cat"
| Dog -> printfn "Its a Dog"
| Spider -> printfn "Its a Spider"
| Snake -> printfn "Its a Snake"
do someFunction SupportedAnimals.Cat
do someFunction SupportedAnimals.Dog
Which when run will give the following results
Matching Discriminated Unions
Whilst the more complex union cases require a bit more work, but it is still quite simple to do, here is an example
type SmallArmyRank =
| General of int
| Private
| Seargent
| Colonel
....
....
....
....
let someFunction x =
match x with
| General 1 -> printfn "Its 1 star General"
| General 2 -> printfn "Its 2 star General"
| General y -> printfn "Its %A top brass General" y
| Private -> printfn "Its Private"
do someFunction (SmallArmyRank.General(1))
do someFunction (SmallArmyRank.General(2))
do someFunction (SmallArmyRank.General(4))
do someFunction SmallArmyRank.Private
Which looks like this when run:
Matching Lists
It is also possible to match the different parts of lists, such as head and tail, where you can use the cons operator “::” within the pattern matching. Here is an example:
let theList = [ 1; 2; 3; 4 ]
let someFunction l =
match l with
| head :: tail -> printf "The head of the list is '%A' and the tail is '%A'\r\n " head tail
| [] -> printfn "The list is empty\r\n"
do someFunction theList
do someFunction List.Empty
Which when run produces the following results:
Though I would say more typically you would use some sort of recursive function when dealing with lists, Here is a small demo of how to use recursion and pattern matching for lists
let theList = [ 1; 2; 3; 4 ]
let rec someFunction l =
match l with
| head :: tail ->
printf "The head is '%A'\r\n" head
someFunction tail
| [] -> printfn "The list is empty\r\n"
do someFunction theList
Which when run gives the following results:
You can also match the individual elements of a list, which is known as the “list pattern”, this only works if you know the exact number of elements in the list. Here is a demo of it in action.
let theList3 = [ 1; 2; 3 ]
let theList5 = [ 1; 2; 3; 4; 5; ]
let someFunction list =
match list with
| [] -> 0
| [ _ ] -> 1
| [ _; _ ] -> 2
| [ _; _; _ ] -> 3
| _ -> List.length list
printfn "The list [ 1; 2; 3 ] has a length of %d" (someFunction theList3)
printfn "The list [ 1; 2; 3; 4; 5; ] has a length of %d" (someFunction theList5)
Which when run gives the following results:
Matching Arrays
It is pretty simple to match against arrays too, I should point out though I am crazy I am not crazy enough to delve into pattern matching 2D/3D and 4D arrays, that is up to you. Here is an example matching a 1D array:
let someFunction x =
match x with
| [|var|] -> printfn "Its 1D array with 1 element"
| [|var1;var2|] -> printfn "Its 1D array with 2 elements"
| _ -> printfn "you are on your own buddy"
do someFunction (Array.init 1 (fun a -> 0))
do someFunction (Array.init 2 (fun a -> 0))
do someFunction (Array.init 4 (fun a -> 0))
Which when run gives the following results: