Learn You a Haskell for Great Good! - Miran Lipovaca [17]
Pattern Matching with Tuples
Pattern matching can also be used on tuples. What if we wanted to write a function that takes two vectors in 2D space (represented as pairs) and adds them together? (To add two vectors, we add their x components separately and their y components separately.) Here’s how we might have done this if we didn’t know about pattern matching:
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double)
addVectors a b = (fst a + fst b, snd a + snd b)
Well, that works, but there’s a better way to do it. Let’s modify the function so that it uses pattern matching:
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double)
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
This is much better. It makes it clear that the parameters are tuples, and increases readability by giving names to the tuple components right away. Note that this is already a catchall pattern. The type of addVectors is the same in both cases, so we are guaranteed to get two pairs as parameters:
ghci> :t addVectors
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double)
fst and snd extract the components of pairs. But what about triples? Well, there are no provided functions to extract the third component in a triple, but we can make our own:
first :: (a, b, c) -> a
first (x, _, _) = x
second :: (a, b, c) -> b
second (_, y, _) = y
third :: (a, b, c) -> c
third (_, _, z) = z
The _ character means the same thing it does in list comprehensions. We really don’t care about that part, so we just use a _ to represent a generic variable.
Pattern Matching with Lists and List Comprehensions
You can also use pattern matching in list comprehensions, like this:
ghci> let xs = [(1,3),(4,3),(2,4),(5,3),(5,6),(3,1)]
ghci> [a+b | (a, b) <- xs]
[4,7,6,8,11,4]
If a pattern match fails, the list comprehension will just move on to the next element, and the element that failed won’t be included in the resulting list.
Regular lists can also be used in pattern matching. You can match with the empty list [] or any pattern that involves : and the empty list. (Remember that [1,2,3] is just syntactic sugar for 1:2:3:[].) A pattern like x:xs will bind the head of the list to x and the rest of it to xs. If the list has only a single element, then xs will simply be the empty list.
Note
Haskell programmers use the x:xs pattern often, especially with recursive functions. However, patterns that include the : character will match only against lists of length one or more.
Now that we’ve looked at how to pattern match against lists, let’s make our own implementation of the head function:
head' :: [a] -> a
head' [] = error "Can't call head on an empty list, dummy!"
head' (x:_) = x
After loading the function, we can test it, like this:
ghci> head' [4,5,6]
4
ghci> head' "Hello"
'H'
Notice that if we want to bind something to several variables (even if one of them is just _), we must surround them in parentheses so Haskell can properly parse them.
Also notice the use of the error function. This function takes a string as an argument and generates a runtime error using that string. It essentially crashes your program, so it’s not good to use it too much. (But calling head on an empty list just doesn’t make sense!)
As another example, let’s write a simple function that takes a list and prints its elements out in a wordy, inconvenient format:
tell :: (Show a) => [a] -> String
tell [] = "The list is empty"
tell (x:[]) = "The list has one element: " ++ show x
tell (x:y:[]) = "The list has two elements: " ++ show x ++ " and " ++ show y
tell (x:y:_) = "This list is long. The first two elements are: " ++ show x
++ " and " ++ show y
Note that (x:[]) and (x:y:[]) could be rewritten as [x] and [x,y]. However, we can’t rewrite (x:y:_) using square brackets, because it matches any list of length 2 or more.
Here are some examples of using this function:
ghci> tell [1]
"The list has one element: 1"
ghci> tell [True,False]
"The