Pattern matching is a powerful technique that allows us to follow different code paths depending on the structure, the shape of a value. For example when working with lists, a list can have either the shape [] of an empty list or the shape x:xs of a list with a first element followed by all the remaining elements. Patterns allow us to tap into those shapes.
Patterns describe a desired structure for a value. We can pattern match such a pattern to particular value. This match can succeed or fail, and if it succeeds then variables specified in the pattern get associated with corresponding parts of the value.
The simplest pattern we can probably look at is something like the following:
let (x, y) = (3, 4)This matches the pattern (x, y) with the value (3, 4). since the value is a tuple of length 2 and the pattern is the same, they match, and x is associated to 3 and y is associated to 4.
On the other hand, the following would not match and produce an (unfriendly) error:
let (x, y) = 5The simplest pattern is a single variable itself. So when we type
let x = 4we are actually performing a pattern match of the pattern x with the value 4. The pattern x will match any value and associated x to 4.
Patterns can be encountered in the following situations:
- In function definitions in the place of the function parameters.
- In the left-hand side of an assignment in a
whereorletclause. For examplelet (x, y) = (3, 4)will setxto3andyto4, and it is an example of a pattern match.- In the special
case ... of ...syntax, which we will see in a few moments.
Here are some rules for pattern-matching:
Pattern-matching rules:
- A single variable pattern
xmatches any valuevand associatesxto that value.- An underscore
_matches any value and creates no new associations.- A tuple pattern
(p1, p2, p3, ..., pn)matches any value(v1, v2, v3, ..., vn)of the same arity only if each patternpimatches the corresponding valuevi. It creates the associations created by the component patterns.- A constructor pattern,
C p1 p2 ... pnwill match a constructor valueC v1 v2 ... vnonly if each patternpimatches the corresponding valuevi. It creates the associations created by the component patterns.
This last one is a bit complicated. But as a simple example, imagine a type for card values: It has one variant for the numeric values, and different variants for Jack through King. We then define a function that returns the numeric value of such a card, with figure cards all having value 10:
data CardValue = NumValue Int | Jack | Queen | King
value :: CardValue -> Int
value (NumValue n)  = n
value Jack          = 10
value Queen         = 10
value King          = 10So if we do something like value (NumValue 5) then the pattern-matching mechanism will compare the NumValue 5 value with each of the patterns in the definition of value, and it will find it matching the first pattern, NumValue n, associating the value 5 with the variable n.
As a further example, let’s revisit lists and now look a bit under the hood. We could for example define our own list type with something like:
data MyList t = Empty | Cons t (MyList t) deriving (Eq, Show)This is saying, that an instace of MyList, which needs a parametrized type t is:
Empty, orCons constructor a value of type talong with a list of values of type t.So this way we can create the list [2,3] as follows:
Cons 2 (Cons 3 Empty)In Haskell we more or less have the same thing, with the changes that Empty is really [] and Cons is really the colon operator, so we can write this example as:
2:3:[]We have already seen list patterns informally when we discussed an implementation for the sum function. Let us revisit that function now with all the extra knowledge we have obtained:
-- sum function that adds all the elements in a list.
sum :: Num t => [t] -> t
sum []     = 0
sum (x:xs) = x + sum xsLet’s go through this line by line:
The function sum has the type Num t => [t] -> t, because it takes a list of values that can be added and returns their sum. Thefore the contents of the list must have a type that has an instance of the Num class.
There are two lines defining the sum function, depending on the shape/structure of the list parameter.
sum [] line matches it and the result is 0.(x:xs), and any non-empty list has that form. Therefore the right-hand-side of that expression will be evaluated, with the variable x bound to the first element and the variable xs bound to the list of the remaining elements.As another example, let us write the function and that is given a list of booleans and is supposed to return True if they are all True (or if the list is empty) and False if there is at least one False value in the list. We can write this with pattern-matches thus:
and :: [Bool] -> Bool
and []          = True
and (True:rest) = and rest
and (False:_)   = FalseLet’s take a look at this one.
True, followed by anything. In that case we want to simply check the rest of the list, so we recursively call the allTrue function.False. In this case the result of the function is supposed to be False regardless of what the rest of the list does. Since we don’t care what value the rest of the list takes, we use the wildcard pattern for it.Practice:
or, which returns True whenever there is at least one True value somewhere in the list (and it should return False for the empty list. Start by writing the type of the function and the different pattern cases you would want to consider.length which returns the length of a string.head which returns the first element of the list (use error "empty list" for the empty list case).last which returns the last element of the list (use error "empty list" for the empty list case).maximum which returns the largest value of the list (use error "empty list" for the empty list case).onlyEvens which given a list of integers returns whether the list contains only even numbers.everyOther which given a list build a new list containing every other element from the original list. Make sure your code can handle both even and odd length lists.init which given a list returns a new list containing all but the last element.alternating which given a list of booleans returns True if the booleans keep alternating in value and False otherwise. For example [True, False, True] would return True but [True, False, False, True] would return False because of the two consecutive False entries.