Pattern Matching in Depth
Prerequisites: Enums and algebraic data types
Pattern matching can take apart almost any structure.
Nested patterns
case class Address(city: String, country: String)
case class User(name: String, age: Int, address: Option[Address])
def describe(user: User): String = user match
case User(name, _, Some(Address(_, "UK"))) => s"$name lives in the UK"
case User(name, age, None) if age < 18 => s"$name is a minor with no address"
case User(name, _, Some(Address(city, _))) => s"$name lives in $city"
case User(name, _, None) => s"$name has no address"
@main def nested(): Unit =
println(describe(User("Ada", 36, Some(Address("London", "UK")))))
println(describe(User("Linus", 28, Some(Address("Helsinki", "FI")))))
println(describe(User("Sam", 15, None)))
println(describe(User("Grace", 45, None)))
Ada lives in the UK
Linus lives in Helsinki
Sam is a minor with no address
Grace has no address
Lists and sequences
def summarize(xs: List[Int]): String = xs match
case Nil => "empty"
case List(x) => s"one element: $x"
case List(a, b) => s"two elements: $a and $b"
case first :: rest => s"starts with $first, then ${rest.length} more"
def sum(xs: List[Int]): Int = xs match
case Nil => 0
case head :: tail => head + sum(tail)
@main def lists(): Unit =
println(summarize(Nil))
println(summarize(List(7)))
println(summarize(List(1, 2)))
println(summarize(List(1, 2, 3, 4)))
println(sum(List(1, 2, 3, 4)))
empty
one element: 7
two elements: 1 and 2
starts with 1, then 3 more
10
head :: tail splits a list into its first element and the rest — the
classic pattern for recursive list processing.
Binding, alternatives, and types
def classify(x: Any): String = x match
case n: Int if n < 0 => "negative int"
case 0 | 1 | 2 => "small int"
case s: String if s.isEmpty => "empty string"
case list @ List(_, _, _*) => s"list of ${list.length}"
case (a: Int, b: Int) => s"int pair summing to ${a + b}"
case _ => "other"
@main def binding(): Unit =
List(-5, 1, "", List(1, 2, 3), (2, 3), 3.5).map(classify).foreach(println)
negative int
small int
empty string
list of 3
int pair summing to 5
other
list @ List(...)binds the whole matched value to a name._*matches any number of remaining elements.
Patterns outside match
case class Point(x: Int, y: Int)
@main def elsewhere(): Unit =
val Point(x, y) = Point(3, 4)
println(x + y)
val points = List(Point(1, 2), Point(3, 4))
for Point(a, b) <- points do println(a * b)
val parsed = List("1", "x", "3").collect { case s if s.toIntOption.isDefined => s.toInt }
println(parsed)
7
2
12
List(1, 3)
collect takes a partial function — a set of case clauses — and
keeps (and transforms) only the elements that match.