Scala match expressions and pattern matching (Scala Cookbook examples)

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.

See Also

  • A discussion of getting around type erasure when using match expressions on Stack Overflow
  • My Blue Parrot application
  • The “Type Erasure” documentation