Skip to content

Commit 9dc280a

Browse files
committed
Improve exhaustivity section of pattern matching
1 parent a3a5f45 commit 9dc280a

File tree

1 file changed

+45
-19
lines changed

1 file changed

+45
-19
lines changed

_tour/pattern-matching.md

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ println(showNotification(someVoiceRecording)) // prints You received a Voice Re
134134
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 `_`.
135135

136136
## Pattern guards
137-
Pattern guards are simply boolean expressions which are used to make cases more specific. Just add `if <boolean expression>` after the pattern.
137+
Pattern guards are boolean expressions which are used to make cases more specific. Just add `if <boolean expression>` after the pattern.
138138

139139
{% tabs pattern-matching-5 class=tabs-scala-version %}
140140
{% tab 'Scala 2' for=pattern-matching-5 %}
@@ -231,36 +231,62 @@ def goIdle(device: Device): String = device match
231231

232232
`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).
233233

234-
## Sealed classes
235-
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.
234+
## Sealed types
235+
236+
You may have noticed that in the examples above the base types are qualified
237+
with the keyword `sealed`. This provides extra safety because the compiler
238+
checks that the `cases` of a `match` expression are exhaustive when the base
239+
type is `sealed`.
240+
241+
For instance, in the method `showNotification` defined above, if we forget
242+
one case, say, `VoiceRecording`, the compiler emits a warning:
236243

237244
{% tabs pattern-matching-7 class=tabs-scala-version %}
238245
{% tab 'Scala 2' for=pattern-matching-7 %}
239-
```scala mdoc
240-
sealed trait Furniture
241-
case class Couch(descriptor: String) extends Furniture
242-
case class Chair(descriptor: String) extends Furniture
243-
244-
def findPlaceToSit(piece: Furniture): String = piece match {
245-
case Couch(descriptor) => s"Lie on the $descriptor couch"
246-
case Chair(descriptor) => s"Sit on the $descriptor chair"
246+
```scala
247+
def showNotification(notification: Notification): String = {
248+
notification match {
249+
case Email(sender, title, _) =>
250+
s"You got an email from $sender with title: $title"
251+
case SMS(number, message) =>
252+
s"You got an SMS from $number! Message: $message"
253+
}
247254
}
248255
```
249256
{% endtab %}
250257
{% tab 'Scala 3' for=pattern-matching-7 %}
251258
```scala
252-
sealed trait Furniture
253-
case class Couch(descriptor: String) extends Furniture
254-
case class Chair(descriptor: String) extends Furniture
255-
256-
def findPlaceToSit(piece: Furniture): String = piece match
257-
case Couch(descriptor) => s"Lie on the $descriptor couch"
258-
case Chair(descriptor) => s"Sit on the $descriptor chair"
259+
def showNotification(notification: Notification): String =
260+
notification match
261+
case Email(sender, title, _) =>
262+
s"You got an email from $sender with title: $title"
263+
case SMS(number, message) =>
264+
s"You got an SMS from $number! Message: $message"
259265
```
260266
{% endtab %}
261267
{% endtabs %}
262268

263-
This is useful for pattern matching because we don't need a "catch all" case.
269+
This definition produces the following warning:
270+
271+
~~~
272+
match may not be exhaustive.
273+
274+
It would fail on pattern case: VoiceRecording(_, _)
275+
~~~
276+
277+
The compiler even provides examples of input that would fail!
278+
279+
On the flip side, exhaustivity checking requires you to define all the subtypes
280+
of the base type in the same file as the base type (otherwise, the compiler
281+
would not know what are all the possible cases). For instance, if you try
282+
to define a new type of `Notification` outside of the file that defines
283+
the `sealed trait Notfication`, it will produce a compilation error:
284+
285+
~~~
286+
case class Telepathy(message: String) extends Notification
287+
^
288+
Cannot extend sealed trait Notification in a different source file
289+
~~~
264290

265291
## Notes
266292

0 commit comments

Comments
 (0)