Pattern Matching | Haskell - Wyatt's Notes
import Citations from ‘@components/Citations.astro’
What Is Pattern Matching?
Section titled “What Is Pattern Matching?”Pattern matching is a mechanism for checking data against a pattern and deconstructing data into its components. It is one of the most powerful features in Haskell, enabling concise and expressive code that directly reflects the structure of data types.
When you write a function definition with multiple equations, each equation has a pattern on the left side. The compiler matches the argument against these patterns in order, executing the right side of the first matching equation.
Matching on Literals
Section titled “Matching on Literals”Literals are the simplest patterns — they match specific constant values:
-- Matching on integer literalsisZero :: Int -> StringisZero 0 = "zero"isZero _ = "not zero"
-- Matching on character literalsvowel :: Char -> Boolvowel "a' = Truevowel 'e' = Truevowel 'i' = Truevowel 'o' = Truevowel 'u' = Truevowel _ = False
-- Matching on string literals (which are lists of characters)sayHello :: String -> StringsayHello "world" = "Hello, World!"sayHello "haskell" = "Hello, Haskell!"sayHello _ = "Hello, stranger!"Variable Patterns and Wildcards
Section titled “Variable Patterns and Wildcards”Variable Patterns
Section titled “Variable Patterns”A variable pattern matches anything and binds the matched value to that variable:
-- 'x' matches any value and binds itdescribe :: Int -> Stringdescribe x = "The number is " ++ show x
-- 'xs' matches any list and binds itlistLength :: [a] -> IntlistLength xs = length xsWildcard Pattern
Section titled “Wildcard Pattern”The wildcard _ matches anything but does not bind the value. It signals that the matched value is not needed:
-- Using wildcard for the second element of a pairfirstOf :: (a, b) -> afirstOf (a, _) = a
-- Using wildcard for unused partsthird :: (a, b, c) -> cthird (_, _, c) = c
-- Wildcard as catch-allclassify :: Int -> Stringclassify 0 = "zero"classify 1 = "one"classify _ = "other"Important: Variable vs Wildcard
Section titled “Important: Variable vs Wildcard”-- Variable pattern: binds the value-- Using 'x' in multiple equations means DIFFERENT bindingsf True = show x -- ERROR: x is not in scope heref False = show x -- ERROR: x is not in scope here
-- Correct: wildcard or different variablesg True = "true"g False = "false"
-- A variable 'x' in a pattern binds the whole valueh :: (Int, Int) -> Inth (x, _) = x -- x is bound to the first elementh (_, x) = x -- x is bound to the second element-- These are DIFFERENT equations with different bindingsConstructor Patterns
Section titled “Constructor Patterns”Matching on Data Constructors
Section titled “Matching on Data Constructors”Data constructors are the primary mechanism for pattern matching in Haskell. Each constructor defines a shape that can be matched:
data Bool = False | True
data Maybe a = Nothing | Just a
isJust :: Maybe a -> BoolisJust (Just _) = TrueisJust Nothing = False
fromMaybe :: a -> Maybe a -> afromMaybe def Nothing = deffromMaybe _ (Just x) = xNested Constructor Patterns
Section titled “Nested Constructor Patterns”Patterns can be nested arbitrarily deep:
data Tree a = Leaf a | Branch (Tree a) (Tree a)
-- Count leaves in a treecountLeaves :: Tree a -> IntcountLeaves (Leaf _) = 1countLeaves (Branch l r) = countLeaves l + countLeaves r
-- Check if a value exists in a treecontains :: Eq a => a -> Tree a -> Boolcontains x (Leaf y) = x == ycontains x (Branch l r) = contains x l || contains x r
-- Sum of all leaf valuestreeSum :: Num a => Tree a -> atreeSum (Leaf x) = xtreeSum (Branch l r) = treeSum l + treeSum rMatching on Tuples
Section titled “Matching on Tuples”Tuples have a fixed structure that can be deconstructed in patterns:
-- Destructuring a pairswap :: (a, b) -> (b, a)swap (a, b) = (b, a)
-- Extracting componentsgetName :: (String, Int, String) -> StringgetName (name, _, _) = name
getAge :: (String, Int, String) -> IntgetAge (_, age, _) = age
-- Nested tuple destructuringinnerFirst :: ((a, b), c) -> ainnerFirst ((a, _), _) = a
-- Using tuple patterns in list comprehensionspairs :: [(Int, Int)]pairs = [(x, y) | x <- [1..3], y <- [1..3], x /= y]-- => [(1,2),(1,3),(2,1),(2,3),(3,1),(3,2)]Matching on Lists
Section titled “Matching on Lists”List Constructors
Section titled “List Constructors”Lists are built from two constructors: [] (empty list) and : (cons — prepend an element to a list). Pattern matching on lists uses these constructors:
-- Empty listisEmpty :: [a] -> BoolisEmpty [] = TrueisEmpty _ = False
-- Non-empty list: x is head, xs is tailheadOrDefault :: a -> [a] -> aheadOrDefault def [] = defheadOrDefault _ (x:_) = x
-- Matching the first two elementssecond :: [a] -> asecond (_:x:_) = x
-- Matching exactly two elementspairToList :: (a, a) -> [a]pairToList (a, b) = [a, b]
-- Counting length via pattern matchingmyLength :: [a] -> IntmyLength [] = 0myLength (_:xs) = 1 + myLength xsCommon List Patterns
Section titled “Common List Patterns”-- Singleton listisSingleton :: [a] -> BoolisSingleton [_] = TrueisSingleton _ = False
-- Exactly three elementsfirstOfThree :: [a] -> afirstOfThree (x:_) = x
-- Splitting at a specific positionsplitAtTwo :: [a] -> ([a], [a])splitAtTwo (a:b:rest) = ([a, b], rest)splitAtTwo xs = (xs, [])
-- Matching a range of elementsstartsWith :: Eq a => [a] -> [a] -> BoolstartsWith [] _ = TruestartsWith _ [] = FalsestartsWith (x:xs) (y:ys) = x == y && startsWith xs ysAs-Patterns
Section titled “As-Patterns”The as-pattern (@) binds the entire matched value while also allowing deconstruction:
-- Without as-pattern: we lose access to the originalsumFirstTwo :: Num a => [a] -> asumFirstTwo (x:y:_) = x + y
-- With as-pattern: we keep the whole list and also its partssumAndKeep :: Num a => [a] -> (a, [a])sumAndKeep xs@(_:_:_) = (head xs + head (tail xs), xs)sumAndKeep xs = (0, xs)
-- More practical examplecapitaliseFirst :: String -> StringcapitaliseFirst [] = []capitaliseFirst s@(c:cs) = toUpper c : cs-- 's' gives us the whole string, 'c' and 'cs' give us head and tail
-- Transforming while keeping the originalfirstAndRest :: [a] -> (a, [a])firstAndRest xs@(x:_) = (x, xs)firstAndRest [] = error "empty list"
-- Checking a property of the whole while deconstructinglongEnough :: Int -> String -> Maybe StringlongEnough n s@(c:cs) | length s >= n = Just s | otherwise = NothinglongEnough _ [] = NothingCase Expressions
Section titled “Case Expressions”Case expressions allow pattern matching anywhere, not just in function definitions:
-- Case expression: match on any valuedescribeNumber :: Int -> StringdescribeNumber n = case n of 0 -> "zero" 1 -> "one" _ -> "other"
-- Case is useful inside other expressionsaddOrDouble :: Maybe Int -> IntaddOrDouble mx = case mx of Nothing -> 0 Just x -> x + x
-- Nested case expressionsclassifyPair :: (Int, Int) -> StringclassifyPair (a, b) = case (a, b) of (0, 0) -> "origin" (0, _) -> "on y-axis" (_, 0) -> "on x-axis" _ -> case compare a b of LT -> "below diagonal" EQ -> "on diagonal" GT -> "above diagonal"Case vs Function Equations
Section titled “Case vs Function Equations”-- These are equivalent:
-- Using multiple equationsfactorial :: Integer -> Integerfactorial 0 = 1factorial n = n * factorial (n - 1)
-- Using case expressionfactorial :: Integer -> Integerfactorial n = case n of 0 -> 1 _ -> n * factorial (n - 1)
-- Case is needed when the value to match comes from-- an expression, not a function argumentprocessPair :: (Int, Int) -> IntprocessPair (a, b) = case a + b of result | result > 10 -> result * 2 | result > 5 -> result | otherwise -> 0Pattern Guards
Section titled “Pattern Guards”Pattern guards refine pattern matching with boolean conditions:
-- Regular guardsclassify xs | null xs = "empty" | length xs > 5 = "long" | otherwise = "short"
-- Pattern guards: combine patterns with boolean conditions-- Requires PatternGuards extension (enabled by default in GHC)sortPair :: Ord a => (a, a) -> (a, a)sortPair p | (a, b) <- p, a <= b = (a, b) | (a, b) <- p, a > b = (b, a)
-- More useful example: parsing a commandparseCommand :: String -> Maybe (String, String)parseCommand s | (cmd, ': ":args) <- break (== '':") s, not (null cmd) = Just (cmd, args) | otherwise = NothingPattern Matching in Let and Where
Section titled “Pattern Matching in Let and Where”Let Patterns
Section titled “Let Patterns”-- Destructuring in letfirstAndSecond :: [a] -> (a, a)firstAndSecond xs = let (x:y:_) = xs in (x, y)
-- Using let with MaybeprocessMaybe :: Maybe (Int, String) -> StringprocessMaybe m = let (Just (n, s)) = m -- partial! crashes on Nothing in show n ++ ": " ++ s
-- Safe version using caseprocessMaybeSafe :: Maybe (Int, String) -> StringprocessMaybeSafe m = case m of Just (n, s) -> show n ++ ": " ++ s Nothing -> "no data"Where Patterns
Section titled “Where Patterns”-- Where clauses can also use pattern matching-- (though this is less common)bmiTell :: Double -> Double -> Double -> StringbmiTell weight height bmi | bmi < 18.5 = "underweight" | bmi < 25.0 = "normal" | otherwise = "overweight" where bmi = weight / height ^ 2
-- Pattern matching in wheresumFirstTwo :: (Int, Int, Int) -> IntsumFirstTwo triple = a + b where (a, b, _) = tripleExhaustive Matching
Section titled “Exhaustive Matching”Non-Exhaustive Patterns
Section titled “Non-Exhaustive Patterns”When patterns do not cover all possible values, the compiler warns (with -Wall) and the program may crash at runtime:
-- Non-exhaustive: what happens with Nothing?unsafeHead :: Maybe a -> aunsafeHead (Just x) = x-- GHC warning: Pattern match(es) are non-exhaustive-- Runtime error on Nothing: *** Exception: ...Making Patterns Exhaustive
Section titled “Making Patterns Exhaustive”-- Add a catch-all wildcardsafeHead :: Maybe a -> Maybe asafeHead (Just x) = Just xsafeHead Nothing = Nothing
-- Or use Maybe explicitlysafeHead2 :: Maybe a -> Maybe asafeHead2 Nothing = NothingsafeHead2 (Just x) = Just xExhaustiveness and Custom Types
Section titled “Exhaustiveness and Custom Types”data Direction = North | South | East | West
-- Exhaustive: covers all constructorsopposite :: Direction -> Directionopposite North = Southopposite South = Northopposite East = Westopposite West = East
-- With -Wall, GHC warns if any constructor is missingOverlapping Patterns
Section titled “Overlapping Patterns”Patterns are matched top to bottom. More specific patterns should come before general ones:
-- Correct: specific patterns firstdescribe :: Int -> Stringdescribe 0 = "zero"describe 1 = "one"describe 42 = "the answer"describe _ = "other"
-- This works the same but is less readable if specific cases-- are buried among general onesWith guards, order matters because the first True guard wins:
-- Order matters heregrade :: Int -> Chargrade score | score >= 90 = 'A' | score >= 80 = 'B' | score >= 70 = 'C' | score >= 60 = 'D' | otherwise = 'F'
-- This would be wrong if reordered:-- | score >= 60 = 'D'-- | score >= 90 = 'A' -- unreachable!View Patterns
Section titled “View Patterns”The ViewPatterns extension allows pattern matching through a function (a “view”):
{-# LANGUAGE ViewPatterns #-}
-- Instead of:process :: String -> Stringprocess s = case length s of 0 -> "empty" 1 -> "singleton" n | n > 10 -> "long" | otherwise -> "medium"
-- With view patterns:process :: String -> Stringprocess (length -> 0) = "empty"process (length -> 1) = "singleton"process (length -> n) | n > 10 = "long" | otherwise = "medium"
-- Custom view functionsisPositive :: Int -> Maybe IntisPositive n | n > 0 = Just n | otherwise = Nothing
safeDiv :: Int -> Int -> Maybe IntsafeDiv _ 0 = NothingsafeDiv x (isPositive -> Just y) = Just (x `div` y)safeDiv _ _ = NothingData vs Newtype
Section titled “Data vs Newtype”data Declaration
Section titled “data Declaration”The data keyword introduces a new algebraic data type. It creates a new type with new constructors:
-- data introduces a new type with runtime overhead-- (a wrapper is allocated on the heap)data Score = Score Int deriving (Show, Eq)
getScore :: Score -> IntgetScore (Score n) = n
-- Multiple constructorsdata Shape = Circle Double Double Double -- x, y, radius | Rectangle Double Double Double Double -- x, y, width, height | Triangle Double Double Double Double Double Double -- three points deriving (Show, Eq)newtype Declaration
Section titled “newtype Declaration”The newtype keyword creates a type that is identical to an existing type at runtime but is a distinct type at compile time. There is zero runtime overhead:
-- newtype has no runtime overhead-- It is erased during compilationnewtype UserId = UserId Int deriving (Show, Eq)
newtype Username = Username String deriving (Show, Eq, Eq)
-- These are different types -- you cannot mix them-- UserId 5 and Int 5 are not interchangeable-- This prevents bugs like passing a UserId where an Int is expecteddata vs newtype Comparison
Section titled “data vs newtype Comparison”-- data: can have multiple constructorsdata Maybe a = Nothing | Just a
-- newtype: exactly one constructor, one fieldnewtype Age = Age Int
-- data: constructor adds a layer of boxingdata Wrapper a = Wrapper a-- Wrapper x evaluates to Wrapper x (lazy in field)
-- newtype: no boxing, isomorphic to the wrapped typenewtype NewWrapper a = NewWrapper a-- NewWrapper x is identical to x at runtimeKey differences:
| Property | data | newtype |
|---|---|---|
| Constructors | One or more | Exactly one |
| Fields | Zero or more | Exactly one |
| Runtime overhead | Yes (heap allocation) | No (erased) |
| Strictness | Lazy by default | Lazy by default |
| Matching | Always matches | Always matches |
| deriving | Full support | Full support + GeneralizedNewtypeDeriving |
GeneralizedNewtypeDeriving
Section titled “GeneralizedNewtypeDeriving”{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype Score = Score Double deriving (Show, Eq, Ord, Num, Enum)
-- This gives Score all Num methods automatically-- (+), (*), (-), abs, signum, fromInteger all work
addScores :: Score -> Score -> ScoreaddScores a b = a + b
-- newtype deriving works because Score is isomorphic to Double-- The compiler directly coerces between Score and DoubleRecord Syntax
Section titled “Record Syntax”Records provide named fields for data types:
-- Basic record typedata Person = Person { personName :: String , personAge :: Int , personEmail :: String } deriving (Show, Eq)
-- Creating recordsalice :: Personalice = Person { personName = "Alice" , personAge = 30 , personEmail = "alice@example.com" }
-- Record field access (automatically generated)getName :: Person -> StringgetName p = personName p
-- Record field update (creates a copy)birthday :: Person -> Personbirthday p = p { personAge = personAge p + 1 }
-- Pattern matching on recordsgreet :: Person -> Stringgreet Person { personName = name, personAge = age } | age < 18 = "Hey " ++ name | otherwise = "Hello " ++ name
-- Record puns: when variable name matches field name-- With RecordWildCards extension{-# LANGUAGE RecordWildCards #-}
makeOlder :: String -> Int -> String -> PersonmakeOlder personName personAge personEmail = Person{..}-- All three fields are bound by their namesRecord Pattern Matching
Section titled “Record Pattern Matching”-- Matching specific fieldsisAdult :: Person -> BoolisAdult Person { personAge = age } = age >= 18
-- Matching multiple fieldscanVote :: Person -> BoolcanVote Person { personAge = age, personEmail = email } = age >= 18 && not (null email)
-- Wildcards for unneeded fieldsgetAge :: Person -> IntgetAge Person { personAge = age } = age-- Only match the age field; name and email are ignoredPattern Matching on Booleans
Section titled “Pattern Matching on Booleans”-- Simple boolean matchingabsolute :: Int -> Intabsolute n | n >= 0 = n | otherwise = -n
-- Using pattern matching directlyclassify :: Bool -> Stringclassify True = "yes"classify False = "no"
-- In case expressionsdescribe :: Bool -> Bool -> Stringdescribe a b = case (a, b) of (True, True) = "both true" (True, False) = "first true" (False, True) = "second true" (False, False) = "both false"Pattern Matching and Recursion
Section titled “Pattern Matching and Recursion”Pattern matching and recursion are deeply intertwined in Haskell:
-- Recursive pattern matching on listsmyMap :: (a -> b) -> [a] -> [b]myMap _ [] = []myMap f (x:xs) = f x : myMap f xs
-- Recursive pattern matching on treesdepth :: Tree a -> Intdepth (Leaf _) = 0depth (Branch l r) = 1 + max (depth l) (depth r)
-- Mutual recursion with pattern matchingisEven, isOdd :: Integral a => a -> BoolisEven 0 = TrueisEven n = isOdd (n - 1)isOdd 0 = FalseisOdd n = isEven (n - 1)
-- Tail-recursive with pattern matching and accumulatormyReverse :: [a] -> [a]myReverse = go [] where go acc [] = acc go acc (x:xs) = go (x : acc) xsPattern Matching Best Practices
Section titled “Pattern Matching Best Practices”- List the most specific patterns first: Patterns are matched top to bottom; a general pattern before specific ones will shadow them.
- Handle all constructors: Use
-Wallto catch non-exhaustive patterns. - Use wildcards
_for unneeded values: This makes the intent clear and avoids unused variable warnings. - Prefer data constructors over guards when the structure is being matched: Constructors make the structure explicit.
- Use newtype for type wrappers: No runtime overhead and clearer intent.
- Use
casewhen matching on computed values: Function equations match only on arguments. - Consider
-Wincomplete-patterns: GHC flag that turns incomplete pattern warnings into errors.
flowchart TD
A[1_Pattern Matching] --> B[Key Concepts]
A --> C[Core Principles]
A --> D[Practical Applications]
B --> E[Fundamental definitions]
C --> F[Design patterns]
D --> G[Real-world usage]Intuition
Section titled “Intuition”Pattern matching in Haskell is like sorting mail into pigeonholes. Each pattern is a pigeonhole with a specific shape, and each value is a piece of mail. The compiler tries to fit the mail into each hole in order, and the first hole that fits determines where the mail goes. This is how Haskell chooses which function clause to execute.
Guards are like bouncers at a club. Even if the shape of the value matches the pattern, the guard checks additional conditions before letting it in. A function clause with a guard is like a VIP section: the door shape matches, but you also need to meet the dress code.
Worked Examples
Section titled “Worked Examples”Example 1: Expression Evaluator with Pattern Matching
Section titled “Example 1: Expression Evaluator with Pattern Matching”Problem: Build a simple arithmetic expression evaluator using algebraic data types and pattern matching.
data Expr = Lit Double | Add Expr Expr | Mul Expr Expr | Neg Expr deriving (Show)
eval :: Expr -> Doubleeval (Lit x) = xeval (Add a b) = eval a + eval beval (Mul a b) = eval a * eval beval (Neg a) = -(eval a)
-- Pretty printer using pattern matchingpretty :: Expr -> Stringpretty (Lit x) = show xpretty (Add a b) = "(" ++ pretty a ++ " + " ++ pretty b ++ ")"pretty (Mul a b) = "(" ++ pretty a ++ " * " ++ pretty b ++ ")"pretty (Neg a) = "(-" ++ pretty a ++ ")"
-- Testexpr :: Exprexpr = Mul (Add (Lit 2) (Lit 3)) (Neg (Lit 4))
-- eval expr => (2 + 3) * (-4) = -20.0-- pretty expr => "((2.0 + 3.0) * (-4.0))"Explanation: Each constructor of Expr represents a different expression form. Pattern matching in eval and pretty dispatches to the correct handling for each form. Adding a new expression type requires only adding a new constructor and new pattern match equations.
Example 2: Binary Tree Operations
Section titled “Example 2: Binary Tree Operations”Problem: Implement common binary tree operations using recursive pattern matching.
data Tree a = Empty | Node (Tree a) a (Tree a) deriving (Show)
-- Insert a value into a BSTinsert :: (Ord a) => a -> Tree a -> Tree ainsert x Empty = Node Empty x Emptyinsert x (Node left val right) | x < val = Node (insert x left) val right | x > val = Node left val (insert x right) | otherwise = Node left val right -- duplicate, no change
-- Search for a valuesearch :: (Ord a) => a -> Tree a -> Maybe asearch _ Empty = Nothingsearch x (Node left val right) | x == val = Just val | x < val = search x left | otherwise = search x right
-- In-order traversal (sorted order for BST)inOrder :: Tree a -> [a]inOrder Empty = []inOrder (Node left val right) = inOrder left ++ [val] ++ inOrder right
-- Tree heightheight :: Tree a -> Intheight Empty = 0height (Node left _ right) = 1 + max (height left) (height right)
-- Build a BST from a listfromList :: (Ord a) => [a] -> Tree afromList = foldl (flip insert) Empty
-- Testbst :: Tree Intbst = fromList [5, 3, 7, 1, 4, 6, 8]
-- inOrder bst => [1, 3, 4, 5, 6, 7, 8]-- search 4 bst => Just 4-- search 9 bst => Nothing-- height bst => 3Explanation: Pattern matching on Empty and Node distinguishes the base case from the recursive case. The guard-based comparison (x < val, x > val, x == val) determines the direction of recursion. Each function follows the same structural pattern: handle Empty first, then deconstruct the Node.
Example 3: JSON Value Processing
Section titled “Example 3: JSON Value Processing”Problem: Process a simplified JSON-like data structure using nested pattern matching and guards.
data JValue = JNull | JBool Bool | JNum Double | JStr String | JArr [JValue] | JObj [(String, JValue)] deriving (Show)
-- Extract a number, returning Nothing if not a numberasNumber :: JValue -> Maybe DoubleasNumber (JNum d) = Just dasNumber _ = Nothing
-- Extract a stringasString :: JValue -> Maybe StringasString (JStr s) = Just sasString _ = Nothing
-- Count elements in any container-like JValuecountElements :: JValue -> IntcountElements (JArr xs) = length xscountElements (JObj kvs) = length kvscountElements _ = 0
-- Pretty print with indentationprettyPrint :: Int -> JValue -> StringprettyPrint _ (JNull) = "null"prettyPrint _ (JBool True) = "true"prettyPrint _ (JBool False)= "false"prettyPrint _ (JNum d) = show dprettyPrint _ (JStr s) = "\"" ++ s ++ "\""prettyPrint indent (JArr xs) = "[\n" ++ concatMap (\v -> replicate (indent + 2) ' ' ++ prettyPrint (indent + 2) v ++ ",\n") xs ++ replicate indent ' ' ++ "]"prettyPrint indent (JObj kvs) = "{\n" ++ concatMap (\(k, v) -> replicate (indent + 2) ' ' ++ "\"" ++ k ++ "\": " ++ prettyPrint (indent + 2) v ++ ",\n") kvs ++ replicate indent ' ' ++ "}"
-- Testjson :: JValuejson = JObj [ ("name", JStr "Alice") , ("age", JNum 30) , ("scores", JArr [JNum 95, JNum 87, JNum 92]) ]
-- countElements json => 3-- asNumber (JNum 42) => Just 42.0-- asString (JStr "hi") => Just "hi"Explanation: Each JValue constructor represents a different JSON type. Pattern matching in prettyPrint handles each case with appropriate formatting. Nested patterns (like JArr xs) bind the contained values for further processing. The indent parameter controls formatting depth for nested structures.
Common Mistakes
Section titled “Common Mistakes”Not handling all cases in pattern matching. When pattern matching on custom types, every constructor must be covered or a catch-all wildcard _ must be provided. GHC warns about non-exhaustive patterns with -Wall, but ignoring the warning leads to runtime crashes when unhandled cases occur. Always ensure all constructors are covered.
Using variable patterns where wildcards are intended. Writing f x = ... in multiple equations means x is rebound in each equation, not compared. If you want to match a specific value, use a literal pattern. If you want to ignore a value, use _. Mixing up variables and wildcards leads to unexpected behaviour.
Forgetting that pattern matching is top-to-bottom. Patterns are evaluated in order, and the first match wins. Placing a general pattern before specific ones shadows the specific cases. For example, f _ = "default" before f 0 = "zero" makes the zero case unreachable.
<Citations sources={[ {title=“Learn You a Haskell for Great Good”, author=“Lipovaca”, year=“2011”, type=“book”}, {title=“Programming in Haskell”, author=“Hutton”, year=“2016”, type=“book”}, ]} />
Cross-References
Section titled “Cross-References”- Types and Functions - How algebraic data types define the patterns that can be matched
- Type Classes - How type class instances interact with pattern matching dispatch
- Monads and Functors - How pattern matching on monadic values enables do-notation desugaring