Pattern Matching (сопоставление с образцом)

Оператор match позволяет выполнить сопоставление значения с образцом.

x = 2
print match x {
  case 1: "One"
  case 2: "Two"
  case "str": "String"
  case _: "Unknown"
}
x = "str"
match x {
  case "": {
    println "Empty string"
  }
  case "str": {
    println "String!"
  }
}

Проверяется тип и значение. Если ни одна из веток case не обнаружила совпадение, выполняется тело ветки case _.

Помимо константных значений, в case может присутствовать имя переменной.

def test(x) = match x {
  case a: "case a: " + a
  case b: "case b: " + b
  case c: "case c: " + c
}
a = 10
b = 20
println test(15)  // case c: 15
println test(20)  // case b: 20
println test("test")  // case c: test

В таком случае возможен один из двух сценариев:

  1. Переменная уже определена. Сравнивается её значение.
  2. Переменная не определена. Ей присваивается сопоставляемое значение и выполняется ветка case.

В примере выше, интерпретатор видит первые две ветки так:

case 10: 
case 20:

Для последней ветки переменная c не определена, поэтому выполнится присваивание c = x, после чего вызов передаётся телу ветки case c.

Уточнения

Ветка case может иметь дополнительное сравнение

def test(x) = match x {
  case x if x < 0: "(-∞ .. 0)"
  case x if x > 0: "(0 .. +∞)"
  case x: "0"
}

println test(-10)  //  (-∞ .. 0)
println test(0)  // 0
println test(10)  //  (0 .. +∞)

Сопоставление массивов

Для сопоставления элементов массивов, в блоке case используется следующий синтаксис:

  • case []: выполняется, если в массиве нет элементов
  • case [a]: выполняется, если в массиве есть один элемент
  • case [a :: b]: выполняется, если в массиве есть два и более элементов
  • case [a :: b :: c :: d :: e]: выполняется, если в массиве есть пять и более элементов

Для двух последних случаев справедливы такие правила:

  • Если количество переменных в списке совпадает с количество элементов массива, то всем переменным присваивается значение массива.
match [0, 1, 2] {
  case [x :: y :: z]: // x = 0, y = 1, z = 2
}
  • Если элементов массива больше, то в последней переменной будут сохранены оставшиеся элементы массива.
match [0, 1, 2, 3, 4] {
  case [x :: y :: z]: // x = 0, y = 1, z = [2, 3, 4]
}

Пример рекурсивного вывода элементов массива

def arrayRecursive(arr) = match arr {
  case [head :: tail]: "[" + head + ", " + arrayRecursive(tail) + "]"
  case []: "[]"
  case last: "[" + last + ", []]"
}

println arrayRecursive([1, 2, 3, 4, 5, 6, 7]) // [1, [2, [3, [4, [5, [6, [7, []]]]]]]]

Сопоставление значений массивов

Для сопоставления значений элементов массивов, используется синтаксис:

  • case (expr1, expr2, expr3): выполняется, если в массиве есть 3 элемента и первый элемент равен результату выражения expr1, второй - результату expr2 и третий - результату expr3.
  • case (expr1, _): выполняется, если в массиве есть 2 элемента и первый элемент равен результату выражения expr1, а результат второго не важен.

Классическая задача FizzBuzz может быть решена с использованием Pattern Matching:

for i = 1, i <= 100, i++ {
  println match [i % 3 == 0, i % 5 == 0] {
    case (true, false): "Fizz"
    case (false, true): "Buzz"
    case (true, true): "FizzBuzz"
    case _: i
  }
}