Программирование на языке Scala/Pattern matching, часть 1

Определение конструкции

править

Pattern matching (сопоставление с шаблоном) - это конструкция, которая позволяет сопоставлять значение с определенными шаблонами (образцами) и выполнять соответствующие действия в зависимости от того, какой шаблон совпадает со значением. Конструкция позволяет удобно описывать множественные ветвления кода программы, условную подмену или преобразование данных.

Конструкция Pattern matching многогранна и имеет богатые возможности, в этом уроке будут рассмотрены только базовые принципы.

Общий вид конструкции - значение match { перечень альтернатив }. Альтернатива представляет собой соединение двух элементов. Первый, это шаблон, с которым значение должно совпасть, и второй, это выражение (или блок выражений), которое будет вычислено при совпадении значения и шаблона.

Если ни один шаблон не совпал со значением, то конструкция выдаст исключительное значение и программа может прервать свое выполнение.

Шаблон, с которым произодится сравнение может быть очень сложным, но пока будем использовать простые шаблоны.

Пример простой конструкции Pattern matching:

val a = 2
val res = a match {
  case 0 => 0
  case 1 => 2 / a
  case 2 => 3 / a // данное выражение будет вычислено, при a = 2
  case _ => 0     // эта альтернатива будет вычислена, если ни один вышестоящий шаблон не совпал.
}

где

  • a - проверяемое значение
  • match - ключевое слово конструкции
  • { ... } - перечень альтернатив
  • case - ключевое слово для начала шаблона альтернативы
  • 0, 1, 2, _ - шаблоны альтернатив
  • => ключевое слово для начала выражения альтернативы

Важно, что конструкция сама является выражением, т.е. вычисляется её значение. Значением конструкции будет значение одного из выражений внутри. Только одна альтернатива будет вычислена.

Последовательность сверки шаблонов

править

Сверка шаблонов производится последовательно, всерху вниз. Если произошло совпадение одного шаблона, то проверок других, ниже лежащих шаблонов, уже не последует, даже если эти шаблоны более точнее подходят к значению.

a match {
  case 0 => 0
  case 0 => 2 / a // Это выражение гарантировано никогда не будет вычисляться
  case _ => 0     
}

Здесь, первая альтернатива case 0 всегда экранирует вторую альтернативу case 0

Такая растановка шаблонов, скорее всего, является логической ошибкой. Компилятор выдаст предупреждение об этом, но разрешит такой код.

Блок выражений альтернативы

править

Выражением альтернативы может быть блок выражений.

a match {
  case 0 => { 
              val b = a + 1
              b * 2
            }  
  case _ => 0     
}

Блоки выражений без фигурных скобок

править

Блок выражений, ради экономии места, можно просто обозначить отспупами перед всеми выражениями блока:

a match {
  case 0 =>  
    val b = a + 1 // отступ 2 символа по отношению к case
    b * 2         // отступ 2 символа по отношению к case
  case _ => 0     
}

Также можно перечень альтернатив не обрамлять фигурными скобками, ограничиться отступами перед case:

a match // нет открывающейся фигурной скобки
  case 0 =>  // отступ 2 символа
    val b = a + 1 
    b * 2         
  case _ => 0 // отступ 2 символа
// нет закрывающейся фигурной скобки

Такой синтаксис может выглядить более лаконично, но необходимо тщательно следить за отступами, чтобы не нарушить последовательность выражений.

Шаблон-литерал

править

Шаблон-литерал соответствует только самому себе. В качестве шаблона может использоваться любой литерал, любого типа. Например, шаблонам-литералами являются 5, 5L, 0x10, true, 'f', '\n', '\u000A', "hello", s"hello $user".

x match
  case 5 => "пять"
  case 5L => "пять"
  case 0x5 => "пять"
  case true => "да"
  case 'f' => "f"
  case '\n' => "перевод строки"
  case '\u000A' => "перевод строки"
  case "hello" => "hello"
  case s"hello $user" => "hello, dude"

Несколько шаблонов в одной альтернативе

править

Можно указать несколько шаблонов в альтернативе, разделив их знаком | (знак логического или). При совпадении любого будет вычислено выражение альтернативы.

x match
  case 0 | 2 | 4 | 6 | 8 => "четное"
  case 1 | 3 | 5 | 7 | 9 => "нечетное"

Типизированный шаблон

править

Данные шаблоны проверяют совпадение значения x с определенным типом

x match
  case _: Int => "тип Int"
  case _: Char => "тип Char"
  case _: String => "тип String"

Использование значения шаблона

править

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

x match
  case _: Int => "тип Int" // Значение шаблона не используется.
  case c: Char => s"тип Char, значение $c" // Значение шаблона имеет имя c и тип Char. Используется в выражении.

Шаблон-внешнее значение

править

Шаблон может представлять собой значение, которое было вычислено заранее. В этом случае имя значения необходимо заключить в обратные ковычки, чтобы не было путаницы с внутреними значениями.

val a = 1
val b = 2
val x = 2
x match
  case `a` => "вариант а" // Шаблон использует внешнее значение a
  case `b` => "вариант b" // Шаблон использует внешнее значение b
  case a => "другой вариант $a" // Значение шаблона имеет внутренее имя a. Шаблон без ограничений.

Шаблон с логическими условиями

править

Шаблону можно указать логические условия его соответствия.

val x = "12345"
x match
  case str: String if str.length > 10 => "длинный" // Логическое условие if str.length > 10 (длина строки должна быть длинее десяти символов)
  case str: String if str.length <= 10 => "короткий" // Логическое условие if str.length <= 10 (длина строки должна быть меньше или равна десяти символам)

Шаблон-конструктор кортежа

править

Шаблон создает кортеж с указанными полями и сравнивает его с входящим кортежем.

val x = (1, 'a') // Кортеж
x match
  case (1, 'a') => "кортеж с первым полем 1 и вторым полем a"
  case (1, ch) => s"кортеж с первым полем 1 и вторым полем $ch"
  case (int: Int, ch: Char) => s"кортеж с первым полем $int, типа Int и вторым полем $ch, типа Char"
  case (_, ch) => s"кортеж с первым любым полем и вторым полем $ch"

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

Порядок указания альтернатив очень важен. Сначала указываем более точные шаблоны, потом более общие, иначе точные шаблоны будут экранированы более общими.

Использование значения шаблона и значений части шаблона одновременно

править

Чтобы иметь значения всего шаблона, а также и его частей в одной альтернативе, нужно для всего шаблона название его значения указать перед знаком @, а частей, в полях шаблона.

val x = (1, 'a') // Кортеж
x match
  case tuple @ (int, ch) => s"кортеж $tuple, с первым полем $int и вторым полем $ch"


Во второй части занятия продолжим изучение шаблонов, но это будет через несколько уроков.

Домашнее задание

править

1. В учебном проекте создайте файл PatternMatching.sc