Pattern Matching in F#
Pattern matching is one of the most useful constructs in F#. Pattern matching
operates on some value, matching the form of that value and altering control
flow thus causing a corresponding expression to be evaluated . Lets take a look
at a simple example:
let actor name = match name with | "Tom Cruise" -> "He is crazy" | _ -> "Not
crazy";;
The LHS (left hand side) should look pretty familiar. Here we've defined a
function called actor that takes one argument, name. On the RHS we've got the
pattern matching construct using the match...with syntax. Here we're matching on
the argument to the function, name. The computation of the matching is
sequential so first it attempts to match the string "Tom Cruise". If the name
value is "Tom Cruise" it will return the string "He is Crazy". If it does not
match that string it will move on to the next pattern. In this case the next
pattern is the wildcard pattern which matches everything.
For fun lets try and remove our wildcard pattern and see how the
compiler/interpreter likes that:
let actor name = match name with
| "Tom Cruise" -> "He is crazy";;
warning: Incomplete pattern match.
As you can see, a warning was generated letting us know that our pattern match is
not exhaustive. By exhaustive we mean that not all possible inputs will be
matched. I wonder what happens when we try and call this function using a string
we can't match on:
actor "Corey Feldman";;
Exception of type
'Microsoft.FSharp.Core.MatchFailureException' was thrown
Uh oh. What happens here is that our pattern was modified by the compiler to have
a default path that generates a MatchFailureException exception. So when we
called our function with a string it could match the exception was thrown. As
you can see, it's good practice to define your pattern match expressions to be
exhaustive.
Guarding your patterns
Guarding rules are another tool you can use when dealing with pattern matching. A
guarding rule appears after your pattern and is executed if that pattern
matches. The guarding rule is simply a conditional expression that if true, will
evaluate the expression corresponding to that pattern. If the rule evaluates to
false, control will flow to the next pattern. Below we extend the previous
example to use a guarding rule:
let actor name isIceMan = match name with
| "Tom Cruise" when not isIceMan -> "He
is crazy"
| _ -> "Not crazy";;
Now we've added the additional isIceMan argument to the function. We've also
added a guarding rule to the first pattern to check whether the bool value
isIceMan is true or false. If it's false the first pattern matches and the
function returns "He is crazy". If it's true then we move on and try to match on
the next pattern. Here's how we might use this function:
> actor "Tom Cruise" true;;
val it : string = "Not crazy"
> actor "Tom Cruise"
false;;
val it : string = "He is crazy"
Lets try another pattern that's a little more difficult. Lets say that we have a
list of integers and we want to return the first value in the list if it's even.
If it's not, then we want to return the last value in the list if it's even. If
neither of them are even we want to return 0. How could we do that? First things
first, lets create a little recursive function that will walk through our list
and return the last value:
let rec lastElement list = if (List.length list <= 1) then List.hd list else
lastElement (List.tl list);;
Pretty simple huh? Our recursive function will return the first element in the
list if the length of the list is one or less. If it's not, then it will call
itself with the tail of the list. The List.tl function (tail) will return a list
of everything but the first element in the list. So we'll keep removing one
element until the list only has one element left, which is the last element in
the original list.
Now that we have our function for getting the last element lets try our pattern:
let contrivedExample list = match list with
| _ when List.hd list % 2 = 0 ->
List.hd list
| _ when lastElement list % 2 = 0 -> lastElement list
| _ -> 0;;
Pretty simple huh? Our first pattern checks the head of the list and the second
pattern uses our recursive function to check the last element. If neither is
even, we return zero.