Pattern matching and recursion are core techniques in functional programming that work hand-in-hand to process data structures and implement algorithms.
See also: Algebraic Data Types, Lists and List Comprehensions, Functions and Equations, Higher-Order Functions, Error Handling
Pattern Matching
See: Algebraic Data Types, Lists and List Comprehensions, Functions and Equations
Basic Syntax
Pattern matching is often used in function definitions:
functionName pattern1 = result1
functionName pattern2 = result2
functionName pattern3 = result3The patterns are tried in order until one matches.
Patterns for Different Types
- Numbers and Characters: See Equational Reasoning
- Lists: See Lists and List Comprehensions
- Tuples: See Algebraic Data Types
- ADTs: See Algebraic Data Types
Matching on Numbers and Characters
isZero :: Int -> Bool
isZero 0 = True
isZero _ = False
isVowel :: Char -> Bool
isVowel 'a' = True
isVowel 'e' = True
isVowel 'i' = True
isVowel 'o' = True
isVowel 'u' = True
isVowel _ = FalseMatching on Lists
isEmpty :: [a] -> Bool
isEmpty [] = True
isEmpty _ = False
head' :: [a] -> a
head' [] = error "Empty list"
head' (x:_) = x
tail' :: [a] -> [a]
tail' [] = error "Empty list"
tail' (_:xs) = xs
sum' :: [Int] -> Int
sum' [] = 0
sum' (x:xs) = x + sum' xsMatching on Tuples
fst' :: (a, b) -> a
fst' (x, _) = x
snd' :: (a, b) -> b
snd' (_, y) = y
addPairs :: [(Int, Int)] -> [Int]
addPairs [] = []
addPairs ((x, y):ps) = (x + y) : addPairs psMatching on Algebraic Data Types
data Shape = Circle Float | Rectangle Float Float
area :: Shape -> Float
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h
data Tree a = Leaf | Node a (Tree a) (Tree a)
treeDepth :: Tree a -> Int
treeDepth Leaf = 0
treeDepth (Node _ left right) = 1 + max (treeDepth left) (treeDepth right)Pattern Guards
Pattern guards add conditions to patterns:
absoluteValue :: Int -> Int
absoluteValue n
| n < 0 = -n
| otherwise = n
grade :: Int -> String
grade score
| score >= 90 = "A"
| score >= 80 = "B"
| score >= 70 = "C"
| score >= 60 = "D"
| otherwise = "F"case Expressions
Pattern matching can also be done within expressions using case:
describePair :: (Int, Int) -> String
describePair pair = case pair of
(0, 0) -> "Origin"
(0, _) -> "Y-axis"
(_, 0) -> "X-axis"
(x, y) -> "Point at (" ++ show x ++ "," ++ show y ++ ")"Recursion
See: Functions and Equations, Higher-Order Functions, Lists and List Comprehensions
Anatomy of Recursive Functions
- Base case(s): Simple scenarios where the result is directly computed
- Recursive case(s): Complex scenarios where the result depends on smaller instances of the same problem
List Processing with Recursion
See: Lists and List Comprehensions, Higher-Order Functions
length' :: [a] -> Int
length' [] = 0 -- Base case
length' (_:xs) = 1 + length' xs -- Recursive case
map' :: (a -> b) -> [a] -> [b]
map' _ [] = [] -- Base case
map' f (x:xs) = f x : map' f xs -- Recursive case
filter' :: (a -> Bool) -> [a] -> [a]
filter' _ [] = [] -- Base case
filter' p (x:xs) -- Recursive case
| p x = x : filter' p xs
| otherwise = filter' p xsRecursive Numerical Functions
-- Factorial
factorial :: Integer -> Integer
factorial 0 = 1 -- Base case
factorial n = n * factorial (n-1) -- Recursive case
-- Fibonacci
fibonacci :: Int -> Integer
fibonacci 0 = 0 -- Base case 1
fibonacci 1 = 1 -- Base case 2
fibonacci n = fibonacci (n-1) + fibonacci (n-2) -- Recursive caseTree Traversal with Recursion
See: Algebraic Data Types
-- Sum all values in a tree
treeSum :: Tree Int -> Int
treeSum Leaf = 0
treeSum (Node value left right) = value + treeSum left + treeSum right
-- Map a function over all values in a tree
treeMap :: (a -> b) -> Tree a -> Tree b
treeMap _ Leaf = Leaf
treeMap f (Node value left right) = Node (f value) (treeMap f left) (treeMap f right)Mutual Recursion
See: Equational Reasoning
Functions can also be mutually recursive, calling each other:
isEven :: Int -> Bool
isEven 0 = True
isEven n = isOdd (n-1)
isOdd :: Int -> Bool
isOdd 0 = False
isOdd n = isEven (n-1)Tail Recursion
Tail recursion is a special case where the recursive call is the last operation:
-- Not tail recursive
sum' :: [Int] -> Int
sum' [] = 0
sum' (x:xs) = x + sum' xs -- Must do addition after recursive call
-- Tail recursive version with accumulator
sumTail :: [Int] -> Int
sumTail xs = sumHelper xs 0
where
sumHelper [] acc = acc
sumHelper (x:xs) acc = sumHelper xs (acc + x) -- Recursive call is last operationBenefits of tail recursion:
- Can be optimized by the compiler into iteration
- Doesn’t grow the call stack
- Prevents stack overflow for large inputs
Key Points to Remember
- Pattern matching is a fundamental technique for working with data in Haskell
- Patterns are tried in order; use
_as a catch-all pattern - Recursion replaces loops in functional programming
- Every recursive function needs at least one base case to terminate
- Tail recursion can be more efficient for large inputs