diff --git a/_tour/pattern-matching.md b/_tour/pattern-matching.md index 0dfbb01b4d..3a6bdbce5d 100644 --- a/_tour/pattern-matching.md +++ b/_tour/pattern-matching.md @@ -134,7 +134,7 @@ println(showNotification(someVoiceRecording)) // prints You received a Voice Re The function `showNotification` takes as a parameter the abstract type `Notification` and matches on the type of `Notification` (i.e. it figures out whether it's an `Email`, `SMS`, or `VoiceRecording`). In the `case Email(sender, title, _)` the fields `sender` and `title` are used in the return value but the `body` field is ignored with `_`. ## Pattern guards -Pattern guards are simply boolean expressions which are used to make cases more specific. Just add `if ` after the pattern. +Pattern guards are boolean expressions which are used to make cases more specific. Just add `if ` after the pattern. {% tabs pattern-matching-5 class=tabs-scala-version %} {% tab 'Scala 2' for=pattern-matching-5 %} @@ -231,36 +231,62 @@ def goIdle(device: Device): String = device match `def goIdle` has a different behavior depending on the type of `Device`. This is useful when the case needs to call a method on the pattern. It is a convention to use the first letter of the type as the case identifier (`p` and `c` in this case). -## Sealed classes -Traits and classes can be marked `sealed` which means all subtypes must be declared in the same file. This assures that all subtypes are known. +## Sealed types + +You may have noticed that in the examples above the base types are qualified +with the keyword `sealed`. This provides extra safety because the compiler +checks that the `cases` of a `match` expression are exhaustive when the base +type is `sealed`. + +For instance, in the method `showNotification` defined above, if we forget +one case, say, `VoiceRecording`, the compiler emits a warning: {% tabs pattern-matching-7 class=tabs-scala-version %} {% tab 'Scala 2' for=pattern-matching-7 %} -```scala mdoc -sealed trait Furniture -case class Couch(descriptor: String) extends Furniture -case class Chair(descriptor: String) extends Furniture - -def findPlaceToSit(piece: Furniture): String = piece match { - case Couch(descriptor) => s"Lie on the $descriptor couch" - case Chair(descriptor) => s"Sit on the $descriptor chair" +```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 -sealed trait Furniture -case class Couch(descriptor: String) extends Furniture -case class Chair(descriptor: String) extends Furniture - -def findPlaceToSit(piece: Furniture): String = piece match - case Couch(descriptor) => s"Lie on the $descriptor couch" - case Chair(descriptor) => s"Sit on the $descriptor chair" +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 %} -This is useful for pattern matching because we don't need a "catch all" case. +This definition produces the following warning: + +~~~ +match may not be exhaustive. + +It would fail on pattern case: VoiceRecording(_, _) +~~~ + +The compiler even provides examples of input that would fail! + +On the flip side, exhaustivity checking requires you to define all the subtypes +of the base type in the same file as the base type (otherwise, the compiler +would not know what are all the possible cases). For instance, if you try +to define a new type of `Notification` outside of the file that defines +the `sealed trait Notfication`, it will produce a compilation error: + +~~~ +case class Telepathy(message: String) extends Notification + ^ + Cannot extend sealed trait Notification in a different source file +~~~ ## Notes