Last modified on 1 March 2015, at 07:51

Haskell/Pattern matching

In the previous modules, we introduced and made occasional reference to pattern matching. Now that we have developed some familiarity with the language, it is time to take a proper, deeper look. We will kick-start the discussion with a condensed description, which we will expand upon throughout the chapter:



Pattern matching on what?

Some languages like Perl and Python use the term pattern matching for matching regular expressions against strings. The pattern matching we refer to in Haskell is something completely different. In fact, you're probably best off forgetting what you know about pattern matching for now.[1] Haskell pattern matching is used in the same way as in other ML-like languages: to deconstruct values according to their type specification.

Analysing pattern matchingEdit

Pattern matching is virtually everywhere. For example, consider this definition of map:

map _ []     = []
map f (x:xs) = f x : map f xs

At surface level, there are four different patterns involved, two per equation.





The connection with constructorsEdit

Despite the detailed analysis above, it may seem a little too magical how we break down a list as if we were undoing the effects of the (:) operator. Be careful: this process will not work with any arbitrary operator. For example, one might think of defining a function which uses (++) to chop off the first three elements of a list:


But that will not work. The function (++) is not allowed in patterns. In fact, most other functions that act on lists are similarly prohibited from pattern matching. Which functions, then, are allowed?


data Foo = Bar | Baz Int

Here Bar and Baz are constructors for the type Foo. You can use them for pattern matching Foo values and bind variables to the Int value contained in a Foo constructed with Baz:

f :: Foo -> Int
f Bar     = 1
f (Baz x) = x - 1

This is exactly like showAnniversary and showDate in the Type declarations module. For instance:

showDate :: Date -> String


Why does it work with lists?Edit

As for lists, they are no different from data-defined algebraic data types as far as pattern matching is concerned. It works as if lists were defined with this data declaration (note that the following isn't actually valid syntax: lists are actually too deeply ingrained into Haskell to be defined like this):



Prelude> :t []
[] :: [a]
Prelude> :t (:)

Furthermore, since [x, y, z] is just syntactic sugar for x:y:z:[], we can achieve something like dropThree using pattern matching alone:

dropThree :: [a] -> [a]
dropThree (_:_:_:xs) = xs
dropThree _          = []




Tuple constructorsEdit


fstPlusSnd (x, y) = x + y


Prelude> (,) 5 3

Matching literal valuesEdit


f :: Int -> Int
f 0 = 1
f 1 = 5
f 2 = 2
f _ = -1

is performing pattern matching as well, matching the argument of f with the Int literals 0, 1 and 2, and finally with _ . In general, numeric and character literals can be used in pattern matching on their own[3] as well as together with constructor patterns. For instance, this function

g :: [Int] -> Bool
g (0:[]) = False
g (0:xs) = True
g _ = False


The above considerations are only valid for literal values, so the following will not work:

k = 1
--again, this won't work as expected
h :: Int -> Bool
h k = True
h _ = False
  2. In this section about pattern matching with literal values, we made no mention of the boolean values True and False, but we can do pattern matching with them as well, as demonstrated in the Next steps chapter. Can you guess why we omitted them? (Hint: is there anything distinctive about the way we write boolean values?)

Syntax tricksEdit


Sometimes, when matching a pattern with a value, it may be useful to bind a name to the whole value being matched. As-patterns allow exactly this: they are of the form var@pattern and have the additional effect to bind the name var to the whole value being matched by pattern. For instance, here is a toy variation on the map theme:

contrivedMap f [] = []

contrivedMap passes to the parameter function f not only x but also the undivided list used as argument of each recursive call. Writing it without as-patterns would have been a bit clunky because we would have to either use head or needlessly reconstruct the original value of list, i.e. actually evaluate x:xs on the right side:

contrivedMap f [] = []
Implement scanr, as in the exercise in List processing, but this time using an as-pattern.

Introduction to recordsEdit

For constructors with many elements, records provide a way of naming values in a datatype using the following syntax:



h :: Foo2 -> Int
h Baz2 {bazName=name} = length name
h Bar2 {} = 0


data Foo = Bar | Baz Int
g :: Foo -> Bool
g Bar {} = True
g Baz {} = False


There are further advantages to using record syntax which we will cover records in more detail in the Named fields section of the More on datatypes chapter.

Where we can use pattern matchingEdit




map _ []     = []
map f (x:xs) = f x : map f xs


let expressions and where clausesEdit


y =
    (x:_) = map (*2) [1,2,3]
  in x + 5

Or, equivalently,

y = x + 5


List comprehensionsEdit


data Maybe a = Nothing | Just a


catMaybes :: [Maybe a] -> [a]
catMaybes ms = [ x | Just x <- ms ]


do blocksEdit


putFirstChar = do
    (c:_) <- getLine
    putStrLn [c]



  2. Reasonable for this particular task, and only because it makes sense to expect that dropThree will give [] when applied to a list of, say, two elements. With a different problem, it might not be reasonable to return any list if the first match failed. In a later chapter, we will consider one simple way of dealing with such cases.
  5. The reason why it works this way instead of crashing out on a pattern matching failure has to do with the real nature of list comprehensions: They are actually wrappers for the list monad. We will eventually explain what that means when we discuss monads.