Pattern Matching

Course 1 · Ch 7
Pattern Matching
The real origin of the style rust1-6's match and csharp2-5's own pattern matching both descend from

Every function written since haskell1-4 has already used pattern matching, informally — sumList's []/(x:xs) equations, haskell1-6's own next. This chapter formalizes it, and traces the real lineage to Rust's match and C#'s own switch patterns.

Matching on Constructors

area :: Shape -> Double area (Circle r) = pi * r * r area (Rectangle w h) = w * h

Multiple function equations are pattern matching — each one matches a specific constructor shape, tried top to bottom until one succeeds. This has been in use since Chapter 4 without being named.

case Expressions

describeShape :: Shape -> String describeShape s = case s of Circle r -> "a circle" Rectangle w h -> "a rectangle"

case is the explicit, single-expression form of what multiple top-level equations do implicitly — useful when pattern matching is only part of a function body, not the whole thing.

Guards

classify :: Int -> String classify n | n < 0 = "negative" | n == 0 = "zero" | otherwise = "positive"

A guard (| condition) adds a boolean condition on top of a successful pattern match. This is the direct conceptual ancestor of C#'s own when clause in csharp2-5's switch patterns, and Rust's own match guards — Haskell had this decades before either.

As-Patterns

firstTwo :: [a] -> ([a], a) firstTwo all@(x:_) = (all, x) -- 'all' binds the WHOLE list, x binds just the head

all@(x:_) binds the entire matched value to all while also binding its parts (x) separately — using both the pieces and the whole without re-matching or reconstructing anything. A genuinely nice convenience with no exact equivalent named in either Rust's or C#'s own pattern syntax on this site.

Exhaustiveness Checking

area :: Shape -> Double area (Circle r) = pi * r * r -- missing the Rectangle case — GHC WARNS about this by default

GHC can warn — and, with a flag, hard-error — when a set of patterns doesn't cover every constructor of a type. Name this plainly: Haskell's pattern matching was exhaustiveness-checked long before rust1-6's own match or csharp2-7's own exhaustive switch over sealed types existed. Both of those are, in a real sense, downstream of this exact idea.

Nested Patterns

describe :: Maybe (Tree Int) -> String describe Nothing = "no tree" describe (Just Leaf) = "empty tree" describe (Just (Node _ v _)) = "tree rooted at " ++ show v

Patterns nest arbitrarily deep — matching straight into a Maybe (Tree a) in one shot, with no chain of manual unwrapping required.

FeatureRust matchC# switch (csharp2-5)Haskell — the origin
Exhaustiveness enforcementyes, compile erroryes, over sealed typeswarning by default, error with a flag — decades earlier
Guardsmatch guardswhen clause| guard — the real ancestor of both
Bind whole + parts at once@ bindings, similar spiritnot directly availableas-patterns (x@pattern)
Turn on -Wincomplete-patterns (or make it a hard error)
-Wincomplete-patterns (and -Werror=incomplete-patterns to make it fail the build) catches a missing case at compile time — the same safety Rust's compiler-enforced exhaustiveness gives by default, opt-in but genuinely worth enabling on every project.
A reached incomplete pattern throws a real, unrecoverable exception
Without the flag above, an incomplete pattern match that's actually hit at runtime throws "Non-exhaustive patterns" — a genuine runtime crash, not something GHC's own compile-time warning catches automatically unless it's explicitly promoted to a hard error.

Coding Challenges

Challenge 1

Write a function grade :: Int -> String using guards that returns "A", "B", "C", or "F" based on a numeric score, with otherwise as the final fallback.

📄 View solution
Challenge 2

Write a function summarizeList :: [Int] -> String using an as-pattern that returns a string mentioning both the full list and its first element, for a non-empty list, and a distinct message for the empty list.

📄 View solution
Challenge 3

Write a function over the Shape type from haskell1-6 that deliberately omits one constructor's pattern, compile it with -Wincomplete-patterns, and report the resulting warning text. Then fix it.

📄 View solution

Chapter 7 Quick Reference

  • Multiple function equations ARE pattern matching, already in use since Chapter 4; case expresses the same idea in one spot
  • Guards (| condition) add a boolean check on top of a pattern — the real ancestor of Rust's match guards and C#'s own when clause
  • As-patterns (x@pattern) bind the whole value and its parts simultaneously — no direct equivalent named for Rust or C# on this site
  • Exhaustiveness checking predates rust1-6's match and csharp2-7's sealed-type switch by decades — both are downstream of this idea
  • Patterns nest arbitrarily deep, avoiding manual unwrapping chains
  • -Wincomplete-patterns (or -Werror=incomplete-patterns) catches a missing case at compile time; without it, a reached incomplete match crashes at runtime
  • Next chapter: typeclasses, a first look — the real ancestor of rust2-2's own traits, arriving decades earlier