Как работает pattern matching в Scala?
Pattern Matching в Scala — это мощный механизм сопоставления с образцом, позволяющий проверять и распаковывать значения по структуре, типу, содержимому и даже условиям. Он часто используется вместо длинных if-else, а также при работе с case class, коллекциями, Option, алгебраическими типами данных (ADT) и другими структурами.
1. Базовый синтаксис
Сопоставление с образцом чаще всего используется с конструкцией match:
val x = 2
val result = x match {
case 1 => "one"
case 2 => "two"
case _ => "other"
}
_ — это универсальный шаблон, аналог default в других языках.
2. Pattern Matching по типу
def process(value: Any): String = value match {
case i: Int => s"Int: $i"
case s: String => s"String: $s"
case _ => "Unknown"
}
При совпадении по типу переменная автоматически кастуется, и доступна без дополнительного приведения.
3. Сопоставление с case class
Это одна из самых частых форм использования:
sealed trait Shape
case class Circle(r: Double) extends Shape
case class Rectangle(w: Double, h: Double) extends Shape
def area(shape: Shape): Double = shape match {
case Circle(r) => math.Pi \* r \* r
case Rectangle(w, h) => w \* h
}
Scala автоматически вызывает unapply, чтобы распаковать параметры case class.
4. Pattern Matching в val и аргументах
Можно деструктурировать значения при присваивании:
val (a, b) = (1, 2) // Tuple распакован
case class User(name: String, age: Int)
val User(n, a2) = User("Alice", 30)
5. Использование Option в match
val maybeName: Option\[String\] = Some("Alice")
val greeting = maybeName match {
case Some(name) => s"Hello, $name"
case None => "Hello, stranger"
}
6. Match Guards (условия)
Можно добавлять условия к шаблону:
val num = 5
val res = num match {
case x if x > 0 => "positive"
case x if x < 0 => "negative"
case _ => "zero"
}
7. Деструктуризация коллекций
val list = List(1, 2, 3)
list match {
case Nil => "empty"
case head :: tail => s"head: $head, tail: $tail"
}
Работает с :: оператором — символ конс (Cons) из списков.
8. Использование в функциях
Pattern Matching можно использовать прямо в аргументах:
def describe(opt: Option\[Int\]): String = opt match {
case Some(x) => s"Got: $x"
case None => "Empty"
}
или
val describe: Option\[Int\] => String = {
case Some(x) => s"Got: $x"
case None => "Empty"
}
9. Сопоставление с литералами, списками, кортежами
def matchList(xs: List\[Int\]) = xs match {
case List(1, 2, 3) => "exact match"
case List(1, \_\*) => "starts with 1"
case _ => "other"
}
def matchTuple(t: (Int, String)) = t match {
case (1, "a") => "matched"
case (x, y) => s"got $x and $y"
}
_* в списке означает любое количество элементов — аналог varargs.
10. Вложенные шаблоны
Можно комбинировать уровни сопоставления:
case class Address(city: String)
case class Person(name: String, address: Address)
val p = Person("Bob", Address("London"))
p match {
case Person(n, Address("London")) => s"$n lives in London"
case _ => "Unknown"
}
11. Псевдонимы @
Позволяют сохранить всё значение, удовлетворяющее шаблону:
val list = List(1, 2, 3)
list match {
case nonEmpty @ head :: tail =>
s"Full: $nonEmpty, head: $head"
case Nil => "Empty"
}
12. sealed и exhaustiveness checking
Если тип sealed, компилятор проверяет, что ты учёл все варианты:
sealed trait Expr
case class Num(n: Int) extends Expr
case class Add(a: Expr, b: Expr) extends Expr
def eval(e: Expr): Int = e match {
case Num(n) => n
case Add(a, b) => eval(a) + eval(b)
}
Если забыть вариант — компилятор может предупредить.
13. unapply и кастомные шаблоны
Можно определять собственные unapply методы:
object Even {
def unapply(x: Int): Boolean = x % 2 == 0
}
val result = 4 match {
case Even() => "Even"
case _ => "Odd"
}
14. Вложенные фильтры и кастомные паттерны
Паттерны можно комбинировать:
object Positive {
def unapply(x: Int): Option\[Int\] = if (x > 0) Some(x) else None
}
val n = 42
val res = n match {
case Positive(x) => s"positive: $x"
case _ => "non-positive"
}
15. Использование в for-comprehension
В for можно распаковывать значения:
val pairs = List((1, "a"), (2, "b"))
for ((num, str) <- pairs) {
println(s"$num -> $str")
}
16. Ошибки и поведение при несовпадении
Если ни один шаблон не подошёл, выбрасывается MatchError, если match используется без default (_):
val x = 10
val y = x match {
case 1 => "one"
// нет default, MatchError возможен
}
Pattern Matching — один из краеугольных камней Scala. Он тесно связан с типами, структурой данных, функциональными идиомами и сильно расширяет выразительность языка без необходимости в явных проверках или кастах.