Skip to content
AgileLogics
6 min read

Pattern Matching

Prerequisites: Tuples

Pattern matching lets you define a function by the shape of its input. Equations are tried from top to bottom, and the first one whose pattern matches is used:

describeNumber :: Int -> String
describeNumber 0 = "zero"
describeNumber 1 = "one"
describeNumber _ = "many"

isVowel :: Char -> Bool
isVowel c = c `elem` "aeiou"

main :: IO ()
main = do
  mapM_ (putStrLn . describeNumber) [0, 1, 7]
  print (filter isVowel "functional")
zero
one
many
"uioa"

_ is a wildcard: it matches anything and ignores the value.

Matching lists

A list is either [] or x : xs, so those are the two patterns you match on. Parentheses are required around x : xs:

describeList :: [Int] -> String
describeList [] = "empty"
describeList [x] = "one element: " ++ show x
describeList [x, y] = "two elements: " ++ show x ++ " and " ++ show y
describeList (x : _) = "starts with " ++ show x

safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x : _) = Just x

main :: IO ()
main = do
  mapM_ (putStrLn . describeList) [[], [5], [5, 6], [5, 6, 7]]
  print (safeHead "abc")
  print (safeHead ([] :: [Int]))
empty
one element: 5
two elements: 5 and 6
starts with 5
Just 'a'
Nothing

safeHead never crashes: it returns Nothing for an empty list and Just x otherwise. You'll meet Maybe properly in Level 2.

Matching tuples

addPairs :: (Int, Int) -> (Int, Int) -> (Int, Int)
addPairs (a, b) (c, d) = (a + c, b + d)

third :: (a, b, c) -> c
third (_, _, z) = z

main :: IO ()
main = do
  print (addPairs (1, 2) (10, 20))
  print (third ('a', True, "yes"))
(11,22)
"yes"

case expressions

case does pattern matching anywhere inside an expression:

weekday :: Int -> String
weekday n = case n of
  1 -> "Monday"
  2 -> "Tuesday"
  6 -> "Saturday"
  7 -> "Sunday"
  _ -> "another day"

main :: IO ()
main = do
  putStrLn (weekday 2)
  putStrLn (weekday 5)
  putStrLn (case words "hello pattern matching" of
    (first : rest) -> first ++ " + " ++ show (length rest) ++ " more"
    [] -> "no words")
Tuesday
another day
hello + 2 more

As-patterns

name@pattern gives a name to the whole value while also matching its parts:

firstLetter :: String -> String
firstLetter "" = "empty string"
firstLetter whole@(c : _) = "The first letter of " ++ whole ++ " is " ++ [c]

main :: IO ()
main = putStrLn (firstLetter "Haskell")
The first letter of Haskell is H

Incomplete patterns

If no equation matches, the program crashes. Compile with -Wall (projects created by cabal init turn it on for you; for a single file, run ghc -Wall file.hs) and GHC warns about every case you forgot. Handling every case is one of the habits that makes Haskell programs so reliable.

Practice

Write secondElement :: [a] -> Maybe a that returns the second element of a list, or Nothing if there isn't one.

Show solution
secondElement :: [a] -> Maybe a
secondElement (_ : y : _) = Just y
secondElement _ = Nothing

main :: IO ()
main = do
  print (secondElement [1, 2, 3 :: Int])
  print (secondElement "a")
Just 2
Nothing