Skip to content

Commit db31f0d

Browse files
authored
Merge pull request #717 from travissarles/pattern-matching
Rewrote pattern matching tour
2 parents 15a2e5c + 6f1aed2 commit db31f0d

File tree

1 file changed

+120
-18
lines changed

1 file changed

+120
-18
lines changed

tutorials/tour/_posts/2017-02-13-pattern-matching.md

Lines changed: 120 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,140 @@ num: 12
1010

1111
next-page: singleton-objects
1212
previous-page: case-classes
13+
prerequisite-knowledge: case-classes, string-interpolation, subtyping
1314
---
1415

15-
Scala has a built-in general pattern matching mechanism. It allows to match on any sort of data with a first-match policy.
16-
Here is a small example which shows how to match against an integer value:
16+
Pattern matching is a mechanism for checking a value against a pattern. A successful match can also deconstruct a value into its constituent parts. It is a more powerful version of the `switch` statement in Java and it can likewise be used in place of a series of if/else statements.
1717

18+
## Syntax
19+
A match expression has a value, the `match` keyword, and at least one `case` clause.
1820
```tut
19-
object MatchTest1 extends App {
20-
def matchTest(x: Int): String = x match {
21-
case 1 => "one"
22-
case 2 => "two"
23-
case _ => "many"
24-
}
25-
println(matchTest(3))
21+
import scala.util.Random
22+
23+
val x: Int = Random.nextInt(10)
24+
25+
x match {
26+
case 0 => "zero"
27+
case 1 => "one"
28+
case 2 => "two"
29+
case _ => "many"
30+
}
31+
```
32+
The `val x` above is a random integer between 0 and 10. `x` becomes the left operand of the `match` operator and on the right is an expression with four cases. The last case `_` is a "catch all" case for any number greater than 2. Cases are also called _alternatives_.
33+
34+
Match expressions have a value.
35+
```tut
36+
def matchTest(x: Int): String = x match {
37+
case 1 => "one"
38+
case 2 => "two"
39+
case _ => "many"
2640
}
41+
matchTest(3) // many
42+
matchTest(1) // one
2743
```
44+
This match expression has a type String because all of the cases return String. Therefore, the function `matchTest` returns a String.
2845

29-
The block with the `case` statements defines a function which maps integers to strings. The `match` keyword provides a convenient way of applying a function (like the pattern matching function above) to an object.
46+
## Matching on case classes
3047

31-
Here is a second example which matches a value against patterns of different types:
48+
Case classes are especially useful for pattern matching.
3249

3350
```tut
34-
object MatchTest2 extends App {
35-
def matchTest(x: Any): Any = x match {
36-
case 1 => "one"
37-
case "two" => 2
38-
case y: Int => "scala.Int"
51+
abstract class Notification
52+
53+
case class Email(sender: String, title: String, body: String) extends Notification
54+
55+
case class SMS(caller: String, message: String) extends Notification
56+
57+
case class VoiceRecording(contactName: String, link: String) extends Notification
58+
59+
60+
```
61+
`Notification` is an abstract super class which has three concrete Notification types implemented with case classes `Email`, `SMS`, and `VoiceRecording`. Now we can do pattern matching on these case classes:
62+
63+
```
64+
def showNotification(notification: Notification): String = {
65+
notification match {
66+
case Email(email, title, _) =>
67+
s"You got an email from $email with title: $title"
68+
case SMS(number, message) =>
69+
s"You got an SMS from $number! Message: $message"
70+
case VoiceRecording(name, link) =>
71+
s"you received a Voice Recording from $name! Click the link to hear it: $link"
72+
}
73+
}
74+
val someSms = SMS("12345", "Are you there?")
75+
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
76+
77+
println(showNotification(someSms)) // prints You got an SMS from 12345! Message: Are you there?
78+
79+
println(showNotification(someVoiceRecording)) // you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
80+
```
81+
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(email, title, _)` the fields `email` and `title` are used in the return value but the `body` field is ignored with `_`.
82+
83+
## Pattern guards
84+
Pattern guards are simply boolean expressions which are used to make cases more specific. Just add `if <boolean expression>` after the pattern.
85+
```
86+
87+
def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {
88+
notification match {
89+
case Email(email, _, _) if importantPeopleInfo.contains(email) =>
90+
"You got an email from special someone!"
91+
case SMS(number, _) if importantPeopleInfo.contains(number) =>
92+
"You got an SMS from special someone!"
93+
case other =>
94+
showNotification(other) // nothing special, delegate to our original showNotification function
3995
}
40-
println(matchTest("two"))
96+
}
97+
98+
val importantPeopleInfo = Seq("867-5309", "[email protected]")
99+
100+
val someSms = SMS("867-5309", "Are you there?")
101+
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
102+
val importantEmail = Email("[email protected]", "Drinks tonight?", "I'm free after 5!")
103+
val importantSms = SMS("867-5309", "I'm here! Where are you?")
104+
105+
println(showImportantNotification(someSms, importantPeopleInfo))
106+
println(showImportantNotification(someVoiceRecording, importantPeopleInfo))
107+
println(showImportantNotification(importantEmail, importantPeopleInfo))
108+
println(showImportantNotification(importantSms, importantPeopleInfo))
109+
```
110+
111+
In the `case Email(email, _, _) if importantPeopleInfo.contains(email)`, the pattern is matched only if the `email` is in the list of important people.
112+
113+
## Matching on type only
114+
You can match on the type like so:
115+
```tut
116+
abstract class Device
117+
case class Phone(model: String) extends Device{
118+
def screenOff = "Turning screen off"
119+
}
120+
case class Computer(model: String) extends Device {
121+
def screenSaverOn = "Turning screen saver on..."
122+
}
123+
124+
def goIdle(device: Device) = device match {
125+
case p: Phone => p.screenOff
126+
case c: Computer => c.screenSaverOn
127+
}
128+
```
129+
`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).
130+
131+
## Sealed classes
132+
Traits and classes can be marked `sealed` which means all subtypes must be declared in the same file. The assures that all subtypes are known.
133+
134+
```tut
135+
sealed abstract class Furniture
136+
case class Couch() extends Furniture
137+
case class Chair() extends Furniture
138+
139+
def findPlaceToSit(piece: Furniture): String = piece match {
140+
case a: Couch => "Lie on the couch"
141+
case b: Chair => "Sit on the chair"
41142
}
42143
```
144+
This is useful for pattern matching because we don't need a "catch all" case.
43145

44-
The first `case` matches if `x` refers to the integer value `1`. The second `case` matches if `x` is equal to the string `"two"`. The third case consists of a typed pattern; it matches against any integer and binds the selector value `x` to the variable `y` of type integer.
146+
## Notes
45147

46148
Scala's pattern matching statement is most useful for matching on algebraic types expressed via [case classes](case-classes.html).
47149
Scala also allows the definition of patterns independently of case classes, using `unapply` methods in [extractor objects](extractor-objects.html).

0 commit comments

Comments
 (0)