Сопоставление с примером (Pattern matching) - это механизм сравнения значений с определенным примером. При успешном совпадении значение может быть разложено на составные части. Мы рассматриваем сопоставление с примером, как более мощную версию switch
оператора из Java. Eго также можно использовать вместо серии if/else выражений.
Синтаксис
Синтаксис сопоставления с примером состоит из значения, ключевого слова match
(сопоставить) и по крайней мере, одного пункта с примером case
, с которым мы хотим сопоставить наше значение.
import scala.util.Random
val x: Int = Random.nextInt(10)
x match {
case 0 => "zero"
case 1 => "one"
case 2 => "two"
case _ => "other"
}
import scala.util.Random
val x: Int = Random.nextInt(10)
x match
case 0 => "zero"
case 1 => "one"
case 2 => "two"
case _ => "other"
Значение константы x
выше представляет собой случайное целое число от 0 до 9. x
становится левым операндом оператора match
, а справа - выражением с четырьмя примерами (называемые еще вариантами). Последний вариант _
- позволяет “поймать все оставшиеся варианты” т. е. для любого числа больше 2.
Сопоставление с примером возвращает значение.
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "other"
}
matchTest(3) // выводит "other"
matchTest(1) // выводит "one"
def matchTest(x: Int): String = x match
case 1 => "one"
case 2 => "two"
case _ => "other"
matchTest(3) // выводит "other"
matchTest(1) // выводит "one"
Это сопоставляющее выражение имеет тип String, так как все варианты сопоставления возвращают String. Поэтому функция matchTest
возвращает String.
Сопоставление с классами образцами
Классы образцы особенно полезны для сопоставления.
sealed trait Notification
case class Email(sender: String, title: String, body: String) extends Notification
case class SMS(caller: String, message: String) extends Notification
case class VoiceRecording(contactName: String, link: String) extends Notification
Notification
- запечатанный трейт, от которого наследуются три конкретных типа реализаций классов образцов Email
, SMS
, и VoiceRecording
. Теперь мы можем делать сопоставление с примером используя в качестве примера один из этих классов образцов.
При сопоставлении с классом образцом мы можем сразу извлекать параметры из которых состоит класс (благодаря автоматическому использованию объекта экстрактора):
def showNotification(notification: Notification): String = {
notification match {
case Email(sender, title, _) =>
s"You got an email from $sender with title: $title"
case SMS(number, message) =>
s"You got an SMS from $number! Message: $message"
case VoiceRecording(name, link) =>
s"You received a Voice Recording from $name! Click the link to hear it: $link"
}
}
val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
println(showNotification(someSms)) // выводит "You got an SMS from 12345! Message: Are you there?"
println(showNotification(someVoiceRecording)) // выводит "You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123"
def showNotification(notification: Notification): String =
notification match
case Email(sender, title, _) =>
s"You got an email from $sender with title: $title"
case SMS(number, message) =>
s"You got an SMS from $number! Message: $message"
case VoiceRecording(name, link) =>
s"You received a Voice Recording from $name! Click the link to hear it: $link"
val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
println(showNotification(someSms)) // выводит "You got an SMS from 12345! Message: Are you there?"
println(showNotification(someVoiceRecording)) // выводит "You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123"
Функция showNotification
принимает в качестве параметра абстрактный тип Notification
который проверяет по образцам (т.е. выясняет, является ли он классом Email
, SMS
или VoiceRecording
). В case Email(email, title, _)
поля email
и title
используются в возвращаемом значении, а вот поле body
игнорируется благодаря символу _
.
Ограждения примеров
Ограждения примеров - это просто логические выражения, которые используются для того, чтобы сделать выбор более специфичным (убрать лишние варианты). Просто добавьте if <логическое выражение>
после примера.
def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {
notification match {
case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
"You got an email from special someone!"
case SMS(number, _) if importantPeopleInfo.contains(number) =>
"You got an SMS from special someone!"
case other =>
showNotification(other) // в этом варианте считается подходящими параметры любого типа. Значит этот вариант выполняется во всех случаях и передает исходный параметр в функцию showNotification
}
}
val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com")
val someSms = SMS("123-4567", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
val importantEmail = Email("jenny@gmail.com", "Drinks tonight?", "I'm free after 5!")
val importantSms = SMS("867-5309", "I'm here! Where are you?")
println(showImportantNotification(someSms, importantPeopleInfo)) // выводит "You got an SMS from 123-4567! Message: Are you there?"
println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) // выводит "You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123"
println(showImportantNotification(importantEmail, importantPeopleInfo)) // выводит "You got an email from special someone!"
println(showImportantNotification(importantSms, importantPeopleInfo)) // выводит "You got an SMS from special someone!"
def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String =
notification match
case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
"You got an email from special someone!"
case SMS(number, _) if importantPeopleInfo.contains(number) =>
"You got an SMS from special someone!"
case other =>
showNotification(other) // в этом варианте считается подходящими параметры любого типа. Значит этот вариант выполняется во всех случаях и передает исходный параметр в функцию showNotification
val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com")
val someSms = SMS("123-4567", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
val importantEmail = Email("jenny@gmail.com", "Drinks tonight?", "I'm free after 5!")
val importantSms = SMS("867-5309", "I'm here! Where are you?")
println(showImportantNotification(someSms, importantPeopleInfo)) // выводит "You got an SMS from 123-4567! Message: Are you there?"
println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) // выводит "You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123"
println(showImportantNotification(importantEmail, importantPeopleInfo)) // выводит "You got an email from special someone!"
println(showImportantNotification(importantSms, importantPeopleInfo)) // выводит "You got an SMS from special someone!"
В варианте case Email(email, _, _) if importantPeopleInfo.contains(email)
, пример сравнивается только если email
находится в списке importantPeopleInfo
.
Сопоставление только с типом
Вы можете сопоставлять только по типу как в примере:
sealed trait Device
case class Phone(model: String) extends Device {
def screenOff = "Turning screen off"
}
case class Computer(model: String) extends Device {
def screenSaverOn = "Turning screen saver on..."
}
def goIdle(device: Device): String = device match {
case p: Phone => p.screenOff
case c: Computer => c.screenSaverOn
}
sealed trait Device
case class Phone(model: String) extends Device:
def screenOff = "Turning screen off"
case class Computer(model: String) extends Device:
def screenSaverOn = "Turning screen saver on..."
def goIdle(device: Device): String = device match
case p: Phone => p.screenOff
case c: Computer => c.screenSaverOn
метод goIdle
реализует изменение поведения в зависимости от типа Device
. По соглашению в качестве названия варианта используется первая буква типа (в данном случае p
и c
).
Запечатанные типы
Вы могли заметить, что в приведенных выше примерах базовые типы уточняются с помощью ключевого слова sealed
.
Это обеспечивает дополнительную безопасность, поскольку компилятор проверяет,
указаны ли все случаи в выражении match
, если базовым типом является sealed
.
Например, в методе showNotification
, определенном выше,
если мы “забудем” один пример, скажем, VoiceRecording
,
компилятор выдаст предупреждение:
def showNotification(notification: Notification): String = {
notification match {
case Email(sender, title, _) =>
s"You got an email from $sender with title: $title"
case SMS(number, message) =>
s"You got an SMS from $number! Message: $message"
}
}
def showNotification(notification: Notification): String =
notification match
case Email(sender, title, _) =>
s"You got an email from $sender with title: $title"
case SMS(number, message) =>
s"You got an SMS from $number! Message: $message"
Это определение выдает следующее предупреждение:
match may not be exhaustive.
It would fail on pattern case: VoiceRecording(_, _)
Компилятор даже предоставляет примеры входных данных, которые потерпят неудачу при сопоставлении!
С другой стороны, проверка полноты требует, чтобы вы определили все подтипы базового типа в том же файле,
что и базовый тип (иначе бы компилятор не знал все возможные варианты).
Например, если вы попытаетесь определить новый тип Notification
вне файла,
который определяет sealed trait Notification
, это приведет к ошибке компиляции:
case class Telepathy(message: String) extends Notification
^
Cannot extend sealed trait Notification in a different source file
Замечания
Сопоставление с примером наиболее полезно для сопоставления алгебраических типов, выраженных через классы образцы.
Scala также позволяет создавать образцы независимо от классов образцов, через использование метода unapply
в объектах экстракторах.
Дополнительные ресурсы
- Дополнительная информация о сопоставлении с примером доступна в книге Scala.
Contributors to this page:
Contents
- Введение
- Основы
- Единобразие типов
- Классы
- Значения Параметров По умолчанию
- Именованные Аргументы
- Трейты
- Кортежи
- Композиция классов с трейтами
- Функции Высшего Порядка
- Вложенные Методы
- Множественные списки параметров (Каррирование)
- Классы Образцы
- Сопоставление с примером
- Объекты Одиночки
- Регулярные Выражения
- Объект Экстрактор
- Сложные for-выражения
- Обобщенные Классы
- Вариантность
- Верхнее Ограничение Типа
- Нижнее Ограничение Типа
- Внутренние классы
- Члены Абстрактного Типа
- Составные Типы
- Самоописываемые типы
- Контекстные параметры, также известные, как неявные параметры
- Неявные Преобразования
- Полиморфные методы
- Выведение Типа
- Операторы
- Вызов по имени
- Аннотации
- Пакеты и Импорт
- Объекты Пакета