Программирование на языке 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