CodeToLive

Pattern Matching in Haskell

Pattern matching is a powerful feature in Haskell that allows you to deconstruct data structures and bind variables to their components. It's used extensively in function definitions, case expressions, and let bindings.

Basic Pattern Matching

The simplest form of pattern matching is in function definitions:


-- Factorial with pattern matching
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

-- Fibonacci sequence
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
      

Tuple Patterns

You can pattern match on tuples:


-- Extract components of a pair
first :: (a, b) -> a
first (x, _) = x

second :: (a, b) -> b
second (_, y) = y

-- Swap elements of a pair
swap :: (a, b) -> (b, a)
swap (x, y) = (y, x)
      

List Patterns

Lists can be deconstructed using the cons operator (:):


-- Sum of a list
sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs

-- Length of a list
length' :: [a] -> Int
length' [] = 0
length' (_:xs) = 1 + length' xs

-- Match specific list patterns
describeList :: [a] -> String
describeList [] = "Empty list"
describeList [x] = "Singleton list"
describeList [x,y] = "List with two elements"
describeList (x:y:_) = "Long list with at least two elements"
      

Case Expressions

Case expressions provide pattern matching within functions:


-- Case expression example
head' :: [a] -> a
head' xs = case xs of
  [] -> error "empty list"
  (x:_) -> x

-- Pattern matching on Maybe
safeDivide :: Double -> Double -> Maybe Double
safeDivide _ 0 = Nothing
safeDivide x y = Just (x / y)

showResult :: Maybe Double -> String
showResult mx = case mx of
  Nothing -> "Division by zero"
  Just x -> "Result: " ++ show x
      

Pattern Matching in let/where

You can use pattern matching in local bindings:


-- Pattern matching in let
getName :: (String, Int, String) -> String
getName person = 
  let (name, _, _) = person
  in name

-- Pattern matching in where
getAge :: (String, Int, String) -> Int
getAge person = age
  where (_, age, _) = person
      

As-patterns

As-patterns allow you to bind a name to the entire pattern:


-- Using as-patterns
capitalize :: String -> String
capitalize "" = ""
capitalize all@(x:xs) = toUpper x : xs

-- Another example
duplicateFirst :: [a] -> [a]
duplicateFirst [] = []
duplicateFirst list@(x:_) = x : list
      

Guards with Patterns

Combine pattern matching with guards for more complex logic:


-- Pattern guards
bmiTell :: Double -> Double -> String
bmiTell weight height
  | weight / height ^ 2 <= 18.5 = "Underweight"
  | weight / height ^ 2 <= 25.0 = "Normal"
  | weight / height ^ 2 <= 30.0 = "Overweight"
  | otherwise = "Obese"

-- With pattern matching
describePerson :: (String, Int, String) -> String
describePerson (name, age, email)
  | age < 13 = name ++ " is a child"
  | age < 20 = name ++ " is a teenager"
  | otherwise = name ++ " is an adult"
      

Nested Patterns

Patterns can be nested to match complex structures:


-- Nested pattern matching
nestedExample :: [(Int, Char)] -> String
nestedExample [] = "Empty list"
nestedExample [(0, 'a')] = "Special case"
nestedExample ((x, y):zs)
  | x > 0 && y == 'b' = "Positive with b"
  | otherwise = "Other case"

-- Matching nested tuples
coordinates :: ((Int, Int), (Int, Int)) -> Int
coordinates ((x1, y1), (x2, y2)) = abs (x1 - x2) + abs (y1 - y2)
      

View Patterns

View patterns allow applying functions during pattern matching:


{-# LANGUAGE ViewPatterns #-}

-- Using view patterns
isEven :: Int -> Bool
isEven n = n `mod` 2 == 0

processList :: [Int] -> String
processList (filter isEven -> []) = "No even numbers"
processList (filter isEven -> evens) = "Even numbers: " ++ show evens
      
← Previous: Types & Typeclasses | Next: Higher-Order Functions →