match expressions

Сопоставление с образцом (pattern matching) является основой функциональных языков программирования. Scala включает в себя pattern matching, обладающий множеством возможностей.

В самом простом случае можно использовать выражение match, подобное оператору Java switch, сопоставляя на основе целочисленного значения. Как и предыдущие структуры, pattern matching - это действительно выражение, поскольку оно вычисляет результат:

import scala.annotation.switch
val i = 6
// i: Int = 6
val day = (i: @switch) match
case 0 => "Sunday"
case 1 => "Monday"
case 2 => "Tuesday"
case 3 => "Wednesday"
case 4 => "Thursday"
case 5 => "Friday"
case 6 => "Saturday"
case _ => "invalid day"
// day: String = "Saturday"

В примере выше переменная i сопоставляется с числом и если равна от 0 до 6, то в day возвращается день недели. Иной случай обозначается символом _ и если i не равен от 0 до 6, то возвращается значение invalid day.

При написании простых выражений соответствия, подобных этому, рекомендуется использовать аннотацию @switch для переменной i. Эта аннотация содержит предупреждение во время компиляции, если switch не может быть скомпилирован в tableswitch или lookupswitch, которые лучше подходят с точки зрения производительности.

Значение по умолчанию

Когда нужно получить доступ к универсальному значению по умолчанию в pattern matching, достаточно указать имя переменной в левой части оператора case, а затем использовать это имя в правой части оператора:

i match
  case 0 => println("1")
  case 1 => println("2")
  case what => println(s"Получено значение: $what")
// Получено значение: 6  

Переменной можно дать любое допустимое имя. Можно также использовать _ в качестве имени, чтобы игнорировать значение.

Обработка нескольких возможных значений в одной строке

В этом примере показано, как использовать несколько возможных совпадений с образцом в каждом операторе case:

val evenOrOdd = i match
  case 1 | 3 | 5 | 7 | 9 => println("odd")
  case 2 | 4 | 6 | 8 | 10 => println("even")
  case _ => println("some other number")
// even  

Использование if в pattern matching

В pattern matching можно использовать условия:

i match
  case 1 => println("one, a lonely number")
  case x if x == 2 || x == 3 => println("two’s company, three’s a crowd")
  case x if x > 3 => println("4+, that’s a party")
  case _ => println("i’m guessing your number is zero or less")
// 4+, that’s a party  

Ещё пример:

i match
  case a if 0 to 9 contains a => println(s"0-9 range: $a")
  case b if 10 to 19 contains b => println(s"10-19 range: $b")
  case c if 20 to 29 contains c => println(s"20-29 range: $c")
  case _ => println("Hmmm...")
// 0-9 range: 6  

case classes и выражение match

Также можно извлекать поля из case class-ов — и классов, которые имеют правильно написанные методы apply/unapply — и использовать их в pattern matching. Вот пример использования простого case class Person

case class Person(name: String)
def speak(p: Person) = p match
case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo")
case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!")
case _ => println("Watch the Flintstones!")
speak(Person("Fred"))
// Fred says, Yubba dubba doo
speak(Person("Bam Bam"))
// Bam Bam says, Bam bam!
speak(Person("Wilma"))
// Watch the Flintstones!

Использование выражения match в теле метода

Поскольку выражения match возвращают значение, их можно использовать в теле метода. Этот метод принимает значение Matchable в качестве входного параметра и возвращает логическое значение на основе результата выражения соответствия:

def isTruthy(a: Matchable) = a match
  case 0 | "" | false => false
  case _              => true

Входной параметр a определяется как тип Matchable, который является родителем всех типов Scala. Для Matchable может выполняться сопоставление с образцом. Метод реализуется путем сопоставления входных данных, обеспечивая два случая: первый проверяет, является ли заданное значение целым числом 0, пустой строкой или false, и в этом случае возвращает false. Для иных случаев возвращается значение true.

Эти примеры показывают, как работает метод:

isTruthy(0)
// res6: Boolean = false
isTruthy(false)
// res7: Boolean = false
isTruthy("")
// res8: Boolean = false
isTruthy(1)
// res9: Boolean = true
isTruthy(" ")
// res10: Boolean = true
isTruthy(2F)
// res11: Boolean = true

Использование pattern matching в качестве тела метода очень распространено.

Использование различных шаблонов в pattern matching

Для выражения match можно использовать множество различных шаблонов. Например:

Все эти виды шаблонов показаны в следующем примере:

def pattern(x: Matchable): String = x match

  // Сравнение с константой
  case 0 => "ноль"
  case true => "true"
  case "hello" => "строка 'hello'"
  case Nil => "пустой List"

  // Сравнение с последовательностями
  case List(0, _, _) => "список из 3 элементов с 0 в качестве первого элемента"
  case List(1, _*) => "Непустой список, начинающийся с 1, и имеющий любой размер > 0"
  case Vector(1, _*) => "Vector, начинающийся с 1, и имеющий любой размер > 0"

  // Сравнение с кортежами
  case (a, b) => s"получено $a и $b"
  case (a, b, c) => s"получено $a, $b и $c"

  // Сравнение с конструктором класса
  case Person(first, "Alexander") => s"Alexander, first name = $first"
  case Dog("Zeus") => "Собака с именем Zeus"

  // Сравнение по типу
  case s: String => s"получена строка: $s"
  case i: Int => s"получено число: $i"
  case f: Float => s"получено число с плавающей точкой: $f"
  case a: Array[Int] => s"массив чисел: ${a.mkString(",")}"
  case as: Array[String] => s"массив строк: ${as.mkString(",")}"
  case d: Dog => s"Экземпляр класса Dog: ${d.name}"
  case list: List[?] => s"получен List: $list"
  case m: Map[?, ?] => m.toString

  // Сравнение по умолчанию
  case _ => "Unknown"

Дополнительные возможности выражений match

match выражения могут быть объединены в цепочку:

def chain(xs: List[Int]) =
  xs match
    case Nil => "empty"
    case _   => "nonempty"
  match
    case "empty"    => 0
    case "nonempty" => 1

chain(List.empty[Int])
// res12: Int = 0
chain(List(1, 2, 3))
// res13: Int = 1

Ссылки: