# Pattern Matching — Scala

Source: https://www.geekswithgeeks.com/en/scala/p-match

> Destructure data with match, guards, extractors and typed patterns.

## match is a powerful expression

**`match`** compares a value against **patterns** and returns the result of the first matching case. Patterns include **literals** (`case 0 =>`), **variables** (`case n =>`), the **wildcard** `_`, **typed patterns** (`case s: String =>`), **constructor patterns** for case classes and enums (`case Item(sku, qty) =>`), **tuples** (`case (a, b) =>`), **lists** (`case head :: tail =>`, `case Nil =>`), **alternatives** (`case "y" | "yes" =>`), **binders** (`case p @ Payment.Card(_) =>`) and **guards** (`case Item(_, q) if q > 10 =>`). A match that finds nothing throws `MatchError`, and the compiler warns about non-exhaustive matches on sealed types. **Extractor objects** define `unapply` to make custom patterns, for example parsing a PIN code from a string. Pattern matching also appears in `val (a, b) = pair`, in for-comprehension generators and in **partial functions** (`{ case ... }` blocks) passed to `collect`. Prefer matching on ADTs over `isInstanceOf` checks and casts.

## Pattern matching as a sorter

Each case checks shape and conditions, binding the parts it needs.

![A value travelling down a column of stencils of different shapes, stopping at the first one it fits, with extracted parts shown beside it.](assets/figures/scala/section-4-map.svg) — Figure 4.1 — match tries cases in order.

## Patterns on lists, tuples, case classes and extractors

Guards, binders and a custom extractor.

```scala
case class Item(sku: String, qty: Int, pricePaise: Long)

def summarise(items: List[Item]): String = items match
  case Nil                       => "empty cart"
  case Item(sku, 1, _) :: Nil    => s"just one $sku"
  case first :: rest if rest.size > 5 => s"${first.sku} and ${rest.size} more (bulk order)"
  case first :: rest             => s"${first.sku} and ${rest.size} more"

object PinCode:
  def unapply(s: String): Option[Int] =
    if s.matches("""\d{6}""") then Some(s.toInt) else None

def classify(input: Any): String = input match
  case 0                           => "zero"
  case n: Int if n < 0             => "negative number"
  case PinCode(pin)                => s"PIN code $pin"
  case s: String if s.isBlank      => "blank text"
  case (city: String, count: Int)  => s"$count orders in $city"
  case item @ Item(_, qty, _) if qty > 100 => s"bulk item ${item.sku}"
  case _                           => "something else"

println(summarise(List(Item("pen", 1, 4950))))   // just one pen
println(classify("411001"))                       // PIN code 411001
println(classify(("Pune", 12)))                   // 12 orders in Pune
```

## Order matters

Cases are tried top to bottom. Put specific cases (with guards) before general ones; a `case _` or `case first :: rest` placed too early silently hides the cases after it, and the compiler warns that they are unreachable.

**Quiz:** What happens when no case matches a value in a match expression?

- [ ] It returns null
- [ ] The first case is used
- [x] A MatchError is thrown at run time
- [ ] The program fails to compile in every case

*Answer:* A MatchError is thrown at run time. Non-exhaustive matches can fail at run time with MatchError; sealed types let the compiler warn first.
