-
Notifications
You must be signed in to change notification settings - Fork 1k
Update pattern-matching.md in russian #2847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
julienrf
merged 3 commits into
scala:main
from
artemkorsakov:Update_tour_pattern_matching_in_ru
Jul 3, 2023
Merged
Changes from 2 commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,12 @@ prerequisite-knowledge: case-classes, string-interpolation, subtyping | |
Сопоставление с примером (Pattern matching) - это механизм сравнения значений с определенным примером. При успешном совпадении значение может быть разложено на составные части. Мы рассматриваем сопоставление с примером, как более мощную версию `switch` оператора из Java. Eго также можно использовать вместо серии if/else выражений. | ||
|
||
## Синтаксис | ||
|
||
Синтаксис сопоставления с примером состоит из значения, ключевого слова `match` (сопоставить) и по крайней мере, одного пункта с примером `case`, с которым мы хотим сопоставить наше значение. | ||
|
||
{% tabs pattern-matching-1 class=tabs-scala-version %} | ||
{% tab 'Scala 2' for=pattern-matching-1 %} | ||
|
||
```scala mdoc | ||
import scala.util.Random | ||
|
||
|
@@ -22,68 +27,146 @@ x match { | |
case 0 => "zero" | ||
case 1 => "one" | ||
case 2 => "two" | ||
case _ => "many" | ||
case _ => "other" | ||
} | ||
``` | ||
Значение константы `x` выше представляет собой случайное целое число от 0 до 10. `x` становится левым операндом оператора `match`, а справа - выражением с четырьмя примерами (называемые еще _вариантами_). Последний вариант `_` - позволяет "поймать все оставшиеся варианты" т. е. для любого числа больше 2. | ||
|
||
{% endtab %} | ||
{% tab 'Scala 3' for=pattern-matching-1 %} | ||
|
||
```scala | ||
import scala.util.Random | ||
|
||
val x: Int = Random.nextInt(10) | ||
|
||
x match | ||
case 0 => "zero" | ||
case 1 => "one" | ||
case 2 => "two" | ||
case _ => "other" | ||
``` | ||
|
||
{% endtab %} | ||
{% endtabs %} | ||
|
||
Значение константы `x` выше представляет собой случайное целое число от 0 до 10. `x` становится левым операндом оператора `match`, а справа - выражением с четырьмя примерами (называемые еще _вариантами_). Последний вариант `_` - позволяет "поймать все оставшиеся варианты" т. е. для любого числа больше 2. | ||
|
||
Сопоставление с примером возвращает значение. | ||
|
||
{% tabs pattern-matching-2 class=tabs-scala-version %} | ||
{% tab 'Scala 2' for=pattern-matching-2 %} | ||
|
||
```scala mdoc | ||
def matchTest(x: Int): String = x match { | ||
case 1 => "one" | ||
case 2 => "two" | ||
case _ => "many" | ||
case _ => "other" | ||
} | ||
matchTest(3) // many | ||
matchTest(1) // one | ||
matchTest(3) // выводит "other" | ||
matchTest(1) // выводит "one" | ||
``` | ||
|
||
{% endtab %} | ||
|
||
{% tab 'Scala 3' for=pattern-matching-2 %} | ||
|
||
```scala | ||
def matchTest(x: Int): String = x match | ||
case 1 => "one" | ||
case 2 => "two" | ||
case _ => "other" | ||
|
||
matchTest(3) // выводит "other" | ||
matchTest(1) // выводит "one" | ||
``` | ||
|
||
{% endtab %} | ||
{% endtabs %} | ||
|
||
Это сопоставляющее выражение имеет тип String, так как все варианты сопоставления возвращают String. Поэтому функция `matchTest` возвращает String. | ||
|
||
## Сопоставление с классами образцами | ||
|
||
Классы образцы особенно полезны для сопоставления. | ||
Классы образцы особенно полезны для сопоставления. | ||
|
||
{% tabs notification %} | ||
{% tab 'Scala 2 и 3' for=notification %} | ||
|
||
```scala mdoc | ||
abstract class Notification | ||
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 | ||
``` | ||
|
||
{% endtab %} | ||
{% endtabs %} | ||
|
||
``` | ||
`Notification` - абстрактный суперкласс, от которого наследуются три конкретных типа реализаций классов образцов `Email`, `SMS`, и `VoiceRecording`. Теперь мы можем делать сопоставление с примером используя в качестве примера один из этих классов образцов. | ||
`Notification` - запечатанный трейт, от которого наследуются три конкретных типа реализаций классов образцов `Email`, `SMS`, и `VoiceRecording`. Теперь мы можем делать сопоставление с примером используя в качестве примера один из этих классов образцов. | ||
При сопоставлении с классом образцом мы можем сразу извлекать параметры из которых состоит класс (благодаря автоматическому использованию [объекта экстрактора](extractor-objects.html)): | ||
|
||
``` | ||
{% tabs pattern-matching-4 class=tabs-scala-version %} | ||
{% tab 'Scala 2' for=pattern-matching-4 %} | ||
|
||
```scala | ||
def showNotification(notification: Notification): String = { | ||
notification match { | ||
case Email(email, title, _) => | ||
s"You got an email from $email with title: $title" | ||
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" | ||
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" | ||
println(showNotification(someVoiceRecording)) // выводит "You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123" | ||
``` | ||
|
||
{% endtab %} | ||
{% tab 'Scala 3' for=pattern-matching-4 %} | ||
|
||
```scala | ||
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" | ||
``` | ||
|
||
{% endtab %} | ||
{% endtabs %} | ||
|
||
Функция `showNotification` принимает в качестве параметра абстрактный тип `Notification` который проверяет по образцам (т.е. выясняет, является ли он классом `Email`, `SMS` или `VoiceRecording`). В `case Email(email, title, _)`поля `email` и `title` используются в возвращаемом значении, а вот поле `body` игнорируется благодаря символу `_`. | ||
|
||
## Ограждения примеров | ||
|
||
Ограждения примеров - это просто логические выражения, которые используются для того, чтобы сделать выбор более специфичным (убрать лишние варианты). Просто добавьте `if <логическое выражение>` после примера. | ||
``` | ||
|
||
{% tabs pattern-matching-5 class=tabs-scala-version %} | ||
{% tab 'Scala 2' for=pattern-matching-5 %} | ||
|
||
```scala | ||
def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = { | ||
notification match { | ||
case Email(email, _, _) if importantPeopleInfo.contains(email) => | ||
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!" | ||
|
@@ -94,53 +177,159 @@ def showImportantNotification(notification: Notification, importantPeopleInfo: S | |
|
||
val importantPeopleInfo = Seq("867-5309", "[email protected]") | ||
|
||
val someSms = SMS("867-5309", "Are you there?") | ||
val someSms = SMS("123-4567", "Are you there?") | ||
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") | ||
val importantEmail = Email("[email protected]", "Drinks tonight?", "I'm free after 5!") | ||
val importantSms = SMS("867-5309", "I'm here! Where are you?") | ||
|
||
println(showImportantNotification(someSms, importantPeopleInfo)) | ||
println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) | ||
println(showImportantNotification(importantEmail, importantPeopleInfo)) | ||
println(showImportantNotification(importantSms, importantPeopleInfo)) | ||
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!" | ||
``` | ||
|
||
{% endtab %} | ||
{% tab 'Scala 3' for=pattern-matching-5 %} | ||
|
||
```scala | ||
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", "[email protected]") | ||
|
||
val someSms = SMS("123-4567", "Are you there?") | ||
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") | ||
val importantEmail = Email("[email protected]", "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!" | ||
``` | ||
|
||
{% endtab %} | ||
{% endtabs %} | ||
|
||
В варианте `case Email(email, _, _) if importantPeopleInfo.contains(email)`, пример сравнивается только если `email` находится в списке `importantPeopleInfo`. | ||
|
||
## Сопоставление только с типом | ||
|
||
Вы можете сопоставлять только по типу как в примере: | ||
|
||
{% tabs pattern-matching-6 class=tabs-scala-version %} | ||
{% tab 'Scala 2' for=pattern-matching-6 %} | ||
|
||
```scala mdoc | ||
abstract class Device | ||
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) = device match { | ||
def goIdle(device: Device): String = device match { | ||
case p: Phone => p.screenOff | ||
case c: Computer => c.screenSaverOn | ||
} | ||
``` | ||
|
||
{% endtab %} | ||
{% tab 'Scala 3' for=pattern-matching-6 %} | ||
|
||
```scala | ||
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 | ||
``` | ||
|
||
{% endtab %} | ||
{% endtabs %} | ||
|
||
метод `goIdle` реализует изменение поведения в зависимости от типа `Device`. По соглашению в качестве названия варианта используется первая буква типа (в данном случае `p` и `c`). | ||
|
||
## Запечатанные классы | ||
Трейты и классы могут быть помечены как `sealed` это означает, что подтипы должны быть объявлены в одном файле, гарантируя тем самым, что все подтипы будут известны. | ||
## Запечатанные типы | ||
|
||
```scala mdoc | ||
sealed abstract class Furniture | ||
case class Couch() extends Furniture | ||
case class Chair() extends Furniture | ||
Вы могли заметить, что в приведенных выше примерах базовые типы уточняются с помощью ключевого слова `sealed`. | ||
Это обеспечивает дополнительную безопасность, поскольку компилятор проверяет, | ||
указаны ли все случаи в выражении `match`, если базовым типом является `sealed`. | ||
|
||
Например, в методе `showNotification`, определенном выше, | ||
если мы "забудем" один пример, скажем, `VoiceRecording`, | ||
компилятор выдаст предупреждение: | ||
|
||
def findPlaceToSit(piece: Furniture): String = piece match { | ||
case a: Couch => "Lie on the couch" | ||
case b: Chair => "Sit on the chair" | ||
{% tabs pattern-matching-7 class=tabs-scala-version %} | ||
{% tab 'Scala 2' for=pattern-matching-7 %} | ||
|
||
```scala | ||
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" | ||
} | ||
} | ||
``` | ||
Это полезно для сопоставления с примером, ведь мы будем заранее знать все доступные варианты и нам не нужен вариант "все остальные". | ||
|
||
{% endtab %} | ||
{% tab 'Scala 3' for=pattern-matching-7 %} | ||
|
||
```scala | ||
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" | ||
``` | ||
|
||
{% endtab %} | ||
{% endtabs %} | ||
|
||
Это определение выдает следующее предупреждение: | ||
|
||
``` | ||
match may not be exhaustive. | ||
|
||
It would fail on pattern case: VoiceRecording(_, _) | ||
``` | ||
|
||
Компилятор даже предоставляет примеры входных данных, которые потерпят неудачу при сопоставлении! | ||
|
||
С другой стороны, проверка полноты требует, чтобы вы определили все подтипы базового типа в том же файле, | ||
что и базовый тип (иначе компилятор не знал бы, каковы все возможные случаи). | ||
Например, если вы попытаетесь определить новый тип `Notification` вне файла, | ||
который определяет `sealed trait Notfication`, это приведет к ошибке компиляции: | ||
|
||
``` | ||
case class Telepathy(message: String) extends Notification | ||
^ | ||
Cannot extend sealed trait Notification in a different source file | ||
``` | ||
|
||
## Замечания | ||
|
||
Сопоставление с примером наиболее полезно для сопоставления алгебраических типов, выраженных через [классы образцы](case-classes.html). | ||
Scala также позволяет создавать образцы независимо от классов образцов, через использование метода `unapply` в [объектах экстракторах](extractor-objects.html). | ||
|
||
## Дополнительные ресурсы | ||
|
||
- Дополнительная информация о сопоставлении с примером доступна [в книге Scala](/scala3/book/control-structures.html#match-expressions). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.