This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 3.11, “How to use pattern matching in Scala match expressions.”
Problem
You need to match one or more patterns in a Scala match expression, and the pattern may be a constant pattern, variable pattern, constructor pattern, sequence pattern, tuple pattern, or type pattern.
Solution
Define a case
statement for each pattern you want to match.
The following method shows examples of many different types of patterns you can use in match
expressions:
// In Scala 2, use the 'Any' type for the parameter. // In Scala 3, use the 'Matchable' type instead. def echoWhatYouGaveMe(x: Any): String = x match { // constant patterns case 0 => "zero" case true => "true" case "hello" => "you said 'hello'" case Nil => "an empty List" // sequence patterns case List(0, _, _) => "a three-element list with 0 as the first element" case List(1, _*) => "a list beginning with 1, having any number of elements" case Vector(1, _*) => "a vector starting with 1, having any number of elements" // tuples case (a, b) => s"got $a and $b" case (a, b, c) => s"got $a, $b, and $c" // constructor patterns case Person(first, "Alexander") => s"found an Alexander, first name = $first" case Dog("Suka") => "found a dog named Suka" // typed patterns case s: String => s"you gave me this string: $s" case i: Int => s"thanks for the int: $i" case f: Float => s"thanks for the float: $f" case a: Array[Int] => s"an array of int: ${a.mkString(",")}" case as: Array[String] => s"an array of strings: ${as.mkString(",")}" case d: Dog => s"dog: ${d.name}" case list: List[_] => s"thanks for the List: $list" case m: Map[_, _] => m.toString // the default wildcard pattern case _ => "Unknown" }
The large match
expression in this method shows the different categories of patterns described in the book, Programming in Scala (Artima), by Odersky, et al., including constant patterns, sequence patterns, tuple patterns, constructor patterns, and typed patterns.
You can test this match
expression in a variety of ways. For the purposes of this example, I created the following object to test the echoWhatYouGaveMe
method:
object LargeMatchTest extends App { case class Person(firstName: String, lastName: String) case class Dog(name: String) // trigger the constant patterns println(echoWhatYouGaveMe(0)) println(echoWhatYouGaveMe(true)) println(echoWhatYouGaveMe("hello")) println(echoWhatYouGaveMe(Nil)) // trigger the sequence patterns println(echoWhatYouGaveMe(List(0,1,2))) println(echoWhatYouGaveMe(List(1,2))) println(echoWhatYouGaveMe(List(1,2,3))) println(echoWhatYouGaveMe(Vector(1,2,3))) // trigger the tuple patterns println(echoWhatYouGaveMe((1,2))) // two element tuple println(echoWhatYouGaveMe((1,2,3))) // three element tuple // trigger the constructor patterns println(echoWhatYouGaveMe(Person("Melissa", "Alexander"))) println(echoWhatYouGaveMe(Dog("Suka"))) // trigger the typed patterns println(echoWhatYouGaveMe("Hello, world")) println(echoWhatYouGaveMe(42)) println(echoWhatYouGaveMe(42F)) println(echoWhatYouGaveMe(Array(1,2,3))) println(echoWhatYouGaveMe(Array("coffee", "apple pie"))) println(echoWhatYouGaveMe(Dog("Fido"))) println(echoWhatYouGaveMe(List("apple", "banana"))) println(echoWhatYouGaveMe(Map(1->"Al", 2->"Alexander"))) // trigger the wildcard pattern println(echoWhatYouGaveMe("33d")) }
Running this object results in the following output:
zero true you said 'hello' an empty List a three-element list with 0 as the first element a list beginning with 1 and having any number of elements a list beginning with 1 and having any number of elements a vector beginning with 1 and having any number of elements a list beginning with 1 and having any number of elements got 1 and 2 got 1, 2, and 3 found an Alexander, first name = Melissa found a dog named Suka you gave me this string: Hello, world thanks for the int: 42 thanks for the float: 42.0 an array of int: 1,2,3 an array of strings: coffee,apple pie dog: Fido thanks for the List: List(apple, banana) Map(1 -> Al, 2 -> Alexander) you gave me this string: 33d
Note that in the match
expression, the List
and Map
statements that were written like this:
case list: List[_] => s"thanks for the List: $list" case m: Map[_, _] => m.toString
could have been written as this instead:
case m: Map[a, b] => m.toString case list: List[x] => s"thanks for the List: $list"
I prefer the underscore syntax because it makes it clear that I’m not concerned about what’s stored in the List
or Map
. Actually, there are times that I might be interested in what’s stored in the List
or Map
, but because of type erasure in the JVM, that becomes a difficult problem.
When I first wrote this example, I wrote the List
expression as follows:
case l: List[Int] => "List"
If you’re familiar with type erasure on the Java platform, you may know that this won’t work. The Scala compiler kindly lets you know about this problem with this warning message:
Test1.scala:7: warning: non-variable type argument Int in type pattern ↵ List[Int] is unchecked since it is eliminated by erasure case l: List[Int] => "List[Int]" ^
If you’re not familiar with type erasure, I’ve included a link in the See Also section of this recipe that describes how it works on the JVM.
Discussion
Typically when using this technique, your method will expect an instance that inherits from a base class or trait, and then your case
statements will reference subtypes of that base type. This was inferred in the echoWhatYouGaveMe
method, where every Scala type is a subtype of Any
. The following code shows a more obvious example of this technique. In my Blue Parrot application, which either plays a sound file or “speaks” the text it’s given at random intervals, I have a method that looks like this:
import java.io.File sealed trait RandomThing case class RandomFile(f: File) extends RandomThing case class RandomString(s: String) extends RandomThing class RandomNoiseMaker { def makeRandomNoise(t: RandomThing) = t match { case RandomFile(f) => playSoundFile(f) case RandomString(s) => speak(s) } }
The makeRandomNoise
method is declared to take a RandomThing
type, and then the match
expression handles its two subtypes, RandomFile
and RandomString
.
Patterns
The large match
expression in the Solution shows a variety of patterns that are defined in the book Programming in Scala. These patterns are briefly described in the following paragraphs.
Constant patterns
A constant pattern can only match itself. Any literal may be used as a constant. If you specify a 0
as the literal, only an Int
value of 0
will be matched. Examples:
case 0 => "zero" case true => "true"
Variable patterns
This was not shown in the large match example in the Solution — it’s discussed in detail in Recipe 3.10, “Accessing the Value of the Default Case in a Match Expression” — but a variable pattern matches any object just like the _
wildcard character. Scala binds the variable to whatever the object is, which lets you use the variable on the right side of the case statement. For example, at the end of a match
expression you can use the _
wildcard character like this to catch “anything else”:
case _ => s"Hmm, you gave me something ..."
But with a variable pattern you can write this instead:
case foo => s"Hmm, you gave me a $foo"
See Recipe 3.10 for more information.
Constructor patterns
The constructor pattern lets you match a constructor in a case
statement. As shown in the examples, you can specify constants or variable patterns as needed in the constructor pattern:
case Person(first, "Alexander") => s"found an Alexander, first name = $first" case Dog("Suka") => "found a dog named Suka"
Sequence patterns
You can match against sequences like List, Array
, Vector
, etc. Use the _
character to stand for one element in the sequence, and use _*
to stand for “zero or more elements,” as shown in the examples:
case List(0, _, _) => "a three-element list with 0 as the first element" case List(1, _*) => "a list beginning with 1, having any number of elements" case Vector(1, _*) => "a vector beginning with 1 and having any number …"
Tuple patterns
As shown in the examples, you can match tuple patterns and access the value of each element in the tuple. You can also use the _
wildcard if you’re not interested in the value of an element:
case (a, b, c) => s"3-elem tuple, with values $a, $b, and $c" case (a, b, c, _) => s"4-elem tuple: got $a, $b, and $c"
Type patterns
In the following example, str: String
is a typed pattern, and str
is a pattern variable:
case str: String => s"you gave me this string: $str"
As shown in the examples, you can access the pattern variable on the right side of the expression after declaring it.
Adding variables to patterns
At times you may want to add a variable to a pattern. You can do this with the following general syntax:
variableName @ pattern
As the book, Programming in Scala, states, “This gives you a variable-binding pattern. The meaning of such a pattern is to perform the pattern match as normal, and if the pattern succeeds, set the variable to the matched object just as with a simple variable pattern.”
The usefulness of this is best shown by demonstrating the problem it solves. Suppose you had the List
pattern that was shown earlier:
case List(1, _*) => "a list beginning with 1, having any number of elements"
As demonstrated, this lets you match a List
whose first element is 1
, but so far, the List
hasn’t been accessed on the right side of the expression. When accessing a `List, you know that you can do this:
case list: List[_] => s"thanks for the List: $list"
so it seems like you should try this with a sequence pattern:
case list: List(1, _*) => s"thanks for the List: $list"
Unfortunately, this fails with the following compiler error:
Test2.scala:22: error: '=>' expected but '(' found. case list: List(1, _*) => s"thanks for the List: $list" ^ one error found
The solution to this problem is to add a variable-binding pattern to the sequence pattern:
case list @ List(1, _*) => s"$list"
This code compiles, and works as expected, giving you access to the List
on the right side of the statement.
The following code demonstrates this example and the usefulness of this approach:
case class Person(firstName: String, lastName: String) object Test2 extends App { def matchType(x: Any): String = x match { //case x: List(1, _*) => s"$x" // doesn't compile case x @ List(1, _*) => s"$x" // works; prints the list //case Some(_) => "got a Some" // works, but can't access the Some //case Some(x) => s"$x" // works, returns "foo" case x @ Some(_) => s"$x" // works, returns "Some(foo)" case p @ Person(first, "Doe") => s"$p" // works, returns "Person(John,Doe)" } println(matchType(List(1,2,3))) // prints "List(1, 2, 3)" println(matchType(Some("foo"))) // prints "Some(foo)" println(matchType(Person("John", "Doe"))) // prints "Person(John,Doe)" }
In the two List
examples inside the match
expression, the commented-out line of code won’t compile, but the second example shows how to assign the variable x
to the List object it matches. When this line of code is matched with the println(matchType(List(1,2,3)))
call, it results in the output List(1, 2, 3)
.
The first Some
example shows that you can match a Some
with the approach shown, but you can’t access its information on the righthand side of the expression. The second example shows how you can access the value inside the Some
, and the third example takes this a step further, giving you access to the Some
object itself. When it’s matched by the second println
call, it prints Some(foo)
, demonstrating that you now have access to the Some
object.
Finally, this approach is used to match a Person
whose last name is Doe
. This syntax lets you assign the result of the pattern match to the variable p
, and then access that variable on the right side of the expression.
Using Some and None in match expressions
To round out these examples, you’ll often use Some
and None
with match
expressions. For instance, assume you have a toInt
method defined like this:
def toInt(s: String): Option[Int] = { try { Some(Integer.parseInt(s.trim)) } catch { case e: Exception => None } }
In some situations, you may want to use this method with a match
expression, like this:
toInt("42") match { case Some(i) => println(i) case None => println("That wasn't an Int.") }
Inside the match
expression you just specify the Some
and None
cases as shown to handle the success and failure conditions. See Recipe 20.6 for more examples of using Option
, Some
, and None
.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
See Also
- A discussion of getting around type erasure when using
match
expressions on Stack Overflow - My Blue Parrot application
- The “Type Erasure” documentation