As well as providing switch-like functionality (that’s more powerful than Java’s version), pattern matching offers a rich set of “patterns” that can be used to match against. In this chapter, we’ll look at the anatomy of patterns and talk through some examples, including literal, constructor and type query patterns.
Pattern matching also provides the ability to deconstruct matched objects, giving you access to parts of a data structure. We’ll look at the mechanics of deconstruction: extractors, which are basically objects with the special method unapply
implemented.
Let’s start by looking at the pattern match expression from earlier.
val
month
=
"August"
val
quarter
=
month
match
{
case
"January"
|
"February"
|
"March"
=>
"1st quarter"
case
"April"
|
"May"
|
"June"
=>
"2nd quarter"
case
"July"
|
"August"
|
"September"
=>
"3rd quarter"
case
"October"
|
"November"
|
"December"
=>
"4th quarter"
case
_
=>
"unknown quarter"
}
There are several key differences between Java’s switch
and Scala’s match
expression:
break
to avoid a fall-through but Scala breaks between each case automatically.Pattern matching also gives us:
if
, we can enrich a case to match not only on the pattern (the part straight after the case
) but also on some binary condition.MatchError
exception letting us know.The anatomy of a match expression looks like this:
value
match
{
case
pattern
guard
=>
expression
...
case
_
=>
default
}
So we have a value, then the match
keyword, followed by a series of match cases. The value can itself be an expression, a literal or even an object.
Each case is made up of a pattern, optionally a guard condition, and the expression to evaluate on a successful match.
You might add a default, catch-all pattern at the end. The underscore is our first example of an actual pattern. It’s the wildcard pattern and means “match on anything”.
A pattern can be:
_
). 101
or RED
.|
).Patterns can also include a variable name, which on matching will be available to the expression on the right-hand side. It’s what’s referred to as a variable ID in the language specification.
There are some more which I’ve left off; if you’re interested see the Pattern Matching section of the .
A literal match is a match against any Scala literal. The example below uses a string literal and has similar semantics to a Java switch statement.
val
language
=
"French"
value
match
{
case
"french"
=>
println
(
"Salut"
)
case
"French"
=>
println
(
"Bonjour"
)
case
"German"
=>
println
(
"Guten Tag"
)
case
_
=>
println
(
"Hi"
)
}
The value must exactly match the literal in the case. In the example, the result will be to print “Bonjour” and not “Salut” as the match value has a capital F
. The match is based on equality (==
).
Constructor patterns allow you to match a case against how an object was constructed. Let’s say we have a SuperHero
class that looks like this:
case
class
SuperHero
(
heroName
:
String
,
alterEgo
:
String
,
powers
:
List
[
Ability
])
It’s a regular class with three constructor arguments, but the keyword case
at the beginning designates it as a case class. For now, that just means that Scala will automatically supply a bunch of useful methods for us, like hashCode
, equals
, and toString
.
Given the class and its fields, we can create a match expression like this:
1
object
BasicConstructorPatternExample
extends
App
{
2
val
hero
=
3
new
SuperHero
(
"Batman"
,
"Bruce Wayne"
,
List
(
Speed
,
Agility
,
Strength
))
4
5
hero
match
{
6
case
SuperHero
(
_
,
"Bruce Wayne"
,
_
)
=>
println
(
"I'm Batman!"
)
7
case
SuperHero
(
_
,
_
,
_
)
=>
println
(
"???"
)
8
}
9
}
Using a constructor pattern, it will match for any hero
whose alterEgo
field matches the value “Bruce Wayne” and print “I’m Batman”. For everyone else, it’ll print question marks.
The underscores are used as placeholders for the constructor arguments; you need three on the second case (line 7) because the constructor has three arguments. The underscore means you don’t care what their values are. Putting the value “Bruce Wayne” on line 6 means you do care and that the second argument to the constructor must match it.
With constructor patterns, the value must also match the type. Let’s say that SuperHero
is a subtype of a Person
.
If the hero
variable was actually an instance of Person
and not a SuperHero
, nothing would match. In the case of no match, you’d see a MatchError
exception at runtime. To avoid the MatchError
, you’d need to allow non-SuperHero
types to match. To do that, you could just use a wildcard as a default.
object
BasicConstructorPatternExample
extends
App
{
val
hero
=
new
Person
(
"Joe Ordinary"
)
hero
match
{
case
SuperHero
(
_
,
"Bruce Wayne"
,
_
)
=>
println
(
"I'm Batman!"
)
case
SuperHero
(
_
,
_
,
_
)
=>
println
(
"???"
)
case
_
=>
println
(
"I'm a civilian, don't shoot!"
)
}
}
Patterns can also bind a matched value to a variable. Instead of just matching against a literal (like “Bruce Wayne”) we can use a variable as a placeholder and access a matched value in the expression on the right-hand side. For example, we could ask the question:
“What super-powers does an otherwise unknown person have, if they are a superhero with the alter-ego Bruce Wayne?”
1
def
superPowersFor
(
person
:
Person
)
=
{
2
person
match
{
3
case
SuperHero
(
_
,
"Bruce Wayne"
,
powers
)
=>
powers
4
case
_
=>
List
()
5
}
6
}
7
8
println
(
"Bruce has the following powers "
+
superPowersFor
(
person
))
We’re still matching only on types of SuperHero
with a literal match against their alter-ego, but this time the underscore in the last position on line 3 is replaced with the variable powers
. This means we can use the variable on the right-hand side. In this case, we just return it to answer the question.
Variable binding is one of pattern matching’s key strengths. In practice, it doesn’t make much sense to use a literal value like “Bruce Wayne” as it limits the application. Instead, you’re more likely to replace it with either a variable or wildcard pattern.
object
HeroConstructorPatternExample
extends
App
{
def
superPowersFor
(
person
:
Person
)
=
{
person
match
{
case
SuperHero
(
_
,
_
,
powers
)
=>
powers
case
_
=>
List
()
}
}
}
You’d then use values from the match object as input. To find out what powers Bruce Wayne has, you’d pass in a SuperHero
instance for Bruce.
val
bruce
=
new
SuperHero
(
"Batman"
,
"Bruce Wayne"
,
List
(
Speed
,
Agility
,
Strength
))
println
(
"Bruce has the following powers "
+
superPowersFor
(
bruce
))
The example is a little contrived as we’re using a match expression to return something that we already know. But as we’ve made the superPowersFor
method more general purpose, we could also find out what powers any superhero or regular person has.
val
steve
=
new
SuperHero
(
"Captain America"
,
"Steve Rogers"
,
List
(
Tactics
,
Speed
))
val
jayne
=
new
Person
(
"Jayne Doe"
)
println
(
"Steve has the following powers "
+
superPowersFor
(
steve
))
println
(
"Jayne has the following powers "
+
superPowersFor
(
jayne
))
Using a constructor pattern, you can implicitly match against a type and access its fields. If you don’t care about the fields, you can use a type query to match against just the type.
For example, we could create a method nameFor
to give us a person or superhero’s name, and call it with a list of people. We’d get back either their name, or if they’re a superhero, their alter ego.
1
object
HeroTypePatternExample
extends
App
{
2
3
val
batman
=
4
new
SuperHero
(
"Batman"
,
"Bruce Wayne"
,
List
(
Speed
,
Agility
,
Strength
))
5
val
cap
=
6
new
SuperHero
(
"Captain America"
,
"Steve Rogers"
,
List
(
Tactics
,
Speed
))
7
val
jayne
=
new
Person
(
"Jayne Doe"
)
8
9
def
nameFor
(
person
:
Person
)
=
{
10
person
match
{
11
case
hero
:
SuperHero
=>
hero
.
alterEgo
12
case
person
:
Person
=>
person
.
name
13
}
14
}
15
16
// What's a superhero's alter ego?
17
println
(
"Batman's Alter ego is "
+
nameFor
(
batman
))
18
println
(
"Captain America's Alter ego is "
+
nameFor
(
cap
))
19
println
(
"Jayne's Alter ego is "
+
nameFor
(
jayne
))
20
}
Rather than use a sequence of instanceOf
checks followed by a cast, you can specify a variable and type. In the expression that follows the arrow, the variable can be used as an instance of that type. So on line 11, hero
is magically an instance of SuperHero
and SuperHero
specific methods (like alterEgo
) are available without casting.
When you use pattern matching to deal with exceptions in a try
and catch
, it’s actually type queries that are being used.
try
{
val
url
=
new
URL
(
"http://baddotrobot.com"
)
val
reader
=
new
BufferedReader
(
new
InputStreamReader
(
url
.
openStream
))
var
line
=
reader
.
readLine
while
(
line
!=
null
)
{
line
=
reader
.
readLine
println
(
line
)
}
}
catch
{
case
_:
MalformedURLException
=>
println
(
"Bad URL"
)
case
e
:
IOException
=>
println
(
"Problem reading data : "
+
e
.
getMessage
)
}
The underscore in the MalformedURLException
match shows that you can use a wildcard with type queries if you’re not interested in using the value.
unapply
It’s common to implement the apply
method as a factory-style creation method; a method taking arguments and giving back a new instance. You can think of the special case unapply
method as the opposite of this. It takes an instance and extracts values from it; usually the values that were used to construct it.
Because they extract values, objects that implement unapply
are referred to as extractors.
Given an object, an extractor typically extracts the parameters that would have created that object.
So if we want to use our Customer
in a match expression, we’d add an unapply
method to its companion object.
class
Customer
(
val
name
:
String
,
val
address
:
String
)
object
Customer
{
def
unapply
(???)
=
???
}
An unapply
method always takes an instance of the object you’d like to deconstruct, in our case a Customer
.
object
Customer
{
def
unapply
(
customer
:
Customer
)
=
???
}
It should return either the extracted parts of the object or something to indicate it couldn’t be deconstructed. In Scala, rather than return a null to represent this, we return the option of a result. It’s the same idea as the Optional
class in Java.
object
Customer
{
def
unapply
(
customer
:
Customer
)
:
Option
[
???
]
=
???
}
The last piece of the puzzle is to work out what can optionally be extracted from the object: the type to put in the Option
parameter. If you wanted to be able to extract just the customer name, the return would be Option[String]
, but we want to be able to extract both the name and address (and therefore be able to match on both name and address in a match expression).
The answer is to use a tuple, the data structure we saw earlier. It’s a way of returning multiple pieces of data in a single type.
object
Customer
{
def
unapply
(
customer
:
Customer
)
:
Option
[(
String
, String
)]
=
{
Some
((
customer
.
name
,
customer
.
address
))
}
}
We can now use a pattern match with our customer.
val
customer
=
new
Customer
(
"Bob"
,
"1 Church street"
)
customer
match
{
case
Customer
(
name
,
address
)
=>
println
(
name
+
" "
+
address
)
}
You’ll notice that this looks like our constructor pattern example. That’s because it’s essentially the same thing; we used a case class before which added an unapply
method for us. This time, we created it ourselves. It’s both an extractor and, because there’s a symmetry with the constructor, a constructor pattern.
More specifically, the list of values to extract in a pattern must match those in a class’s primary constructor to be called a constructor pattern. See for details.
Why would you implement your own extractor method (unapply
) when case classes already have one? It might be simply because you can’t or don’t want to use a case class or you may not want the match behaviour of a case class; you might want custom extraction behaviour (for example, returning Boolean
from unapply
to indicate a match with no extraction).
It might also be the case that you can’t modify a class but you’d like to be able to extract parts from it. You can write extractors for anything. For example, you can’t modify the String
class but you still might want to extract things from it, like parts of an email address or a URL.
For example, the stand-alone object below extracts the protocol and host from a string when it’s a valid URL. It has no relationship with the String
class but still allows us to write a match expression and “deconstruct” a string into a protocol and host.
object
UrlExtractor
{
def
unapply
(
string
:
String
)
:
Option
[(
String
, String
)]
=
{
try
{
val
url
=
new
URL
(
string
)
Some
((
url
.
getProtocol
,
url
.
getHost
))
}
catch
{
case
_:
MalformedURLException
=>
None
}
}
}
val
url
=
"http://baddotrobot.com"
match
{
case
UrlExtractor
(
protocol
,
host
)
=>
println
(
protocol
+
" "
+
host
)
}
This decoupling between patterns and the data types they work against is called representation independence (see Section ) of Programming in Scala.
You can complement the patterns we’ve seen with if
conditions.
customer
.
yearsACustomer
=
3
val
discount
=
customer
match
{
case
YearsACustomer
(
years
)
if
years
>=
5
=>
Discount
(
0.50
)
case
YearsACustomer
(
years
)
if
years
>=
2
=>
Discount
(
0.20
)
case
YearsACustomer
(
years
)
if
years
>=
1
=>
Discount
(
0.10
)
case
_
if
blackFriday
(
today
)
=>
Discount
(
0.10
)
case
_
=>
Discount
(
0
)
}
The condition following the pattern is called a guard. You can reference a variable if you like, so we can say for customers of over five years, a 50% discount applies; two years, 20% and so on. If a variable isn’t required, that’s fine too. For example, we’ve got a case that says if no previous discount applies and today is Black Friday, give a discount of 10%.