What pattern matching is
You already know how to branch with if and else if. Scala has a second branching tool, match, that often reads better when one value is tested against many possibilities. It resembles switch in other languages, but it is stronger in two ways:
matchis an expression. The whole thing evaluates to a value, so you can bind the result straight to aval.- Each
caseholds a pattern, not only a constant. A pattern can be a literal, a name that captures the value, a name with a condition attached, a tuple that takes data apart, or the wildcard_that matches anything.
val temp = 3
val advice = temp match {
case t if t >= 30 => "stay in the shade"
case t if t >= 10 => "perfect day"
case _ => "bring a jacket"
}
println(advice) // bring a jacket
Each case reads case pattern => value. Cases are tried from top to bottom, and the first one that fits wins. Its value becomes the value of the whole match. The part t if t >= 30 is a guard: the name t captures the number, and the case only matches when the condition after if is true. Scala has no range pattern, so guards are how you say "30 or above".
The trap: a match with no matching case
An if with no else quietly does nothing when its condition is false. A match does not. If no case fits, the program throws a scala.MatchError at run time:
val n = 55
val r = n match {
case s if s >= 80 => "B"
}
println(r)
Exception in thread "main" scala.MatchError: 55 (of class java.lang.Integer)
The compiler accepts this code. The message names the value that slipped through every case, which is 55 here. The fix is a wildcard case, case _ => ..., which matches anything the earlier cases did not. End every match over an open-ended value, such as an Int, with one.
Matching a tuple
Patterns can take data apart. A tuple such as (0, 5) packs two values together, and a tuple pattern unpacks it and tests both slots at once:
val point = (0, 5)
val location = point match {
case (0, 0) => "origin"
case (0, y) => s"on the y-axis at $y"
case _ => "somewhere else"
}
(0, 0) matches one exact pair. (0, y) is the interesting one: the literal 0 must be equal, while the lowercase name y matches any second value and captures it, so the right side can use it. A pattern both tests the shape and extracts the parts, which is why the feature is called pattern matching and not just switching.
A few more forms are handy. Alternatives are separated with |, as in case "sat" | "sun" => "weekend". The string prefix s in s"... $y" inserts the value of y into the text.
The lesson's exercise
The exercise builds both examples step by step.
- Declare
scoreas avalholding 87. The match needs a value to branch on. - Open the match as an expression. Write
val grade = score match { ... }as one declaration. Noreturnand no assignment inside each branch is needed; whichever case runs becomes the value ofgrade. - Fill in four cases. A constant case for
100gives"perfect". Two guarded cases follow:swiths >= 90gives"A", andswiths >= 80gives"B". The wildcard gives"C". Order matters: if the>= 80guard came before the>= 90one, a 95 would be graded B. - Print the grade with the interpolated string
s"grade = $grade". The>= 80guard catches 87, so the output isgrade = B. - Build
pointas the tuple(0, 5). - Open the second match on
pointand bind it toval location, with the same shape as before. - Add the three tuple cases:
(0, 0)for"origin",(0, y)fors"on the y-axis at $y", and the wildcard for"somewhere else". The pair(0, 5)fails the first case and fits the second, withybound to 5. - Print the location with
s"location = $location".
val score = 87
val grade = score match {
case 100 => "perfect"
case s if s >= 90 => "A"
case s if s >= 80 => "B"
case _ => "C"
}
println(s"grade = $grade")
val point = (0, 5)
val location = point match {
case (0, 0) => "origin"
case (0, y) => s"on the y-axis at $y"
case _ => "somewhere else"
}
println(s"location = $location")
The program prints:
grade = B
location = on the y-axis at 5
Common mistakes
- Leaving out
case _. A value that matches nothing throwsMatchErrorat run time. - Putting a broad case first. Cases are tried in order, and a guard such as
s >= 80would catch every score of 90 or more before theAcase is reached. - Expecting a range pattern. There is none. Use a guard:
case s if s >= 90. - Forgetting that lowercase names capture. In
case (0, y),yis a new variable that matches anything, not a comparison with an existing variable calledy. - Using
=instead of=>between a pattern and its result.
Key takeaways
matchis an expression: it gives a value that you can bind withval.- Cases run top to bottom, and the first matching case wins.
- A guard (
case s if s >= 90) adds a condition to a captured name. - A tuple pattern tests the shape and captures the parts at once.
- Always end a match over an open-ended value with
case _to avoidMatchError.
