Mike Slinn

Pattern Matching

— Draft —

Published 2014-05-01. Last modified 2015-09-22.
Time to read: 8 minutes.

Pattern matching is a core strength of Scala. Although simple in concept, it may take a while for you to readjust how you think about programming once you realize how powerful pattern matching is. The Sealed Classes and Extractors lecture builds on this lecture.

Scala can match on values, types, and a combination of types and values with conditional tests.

The Scala 2 sample code for this lecture can be found in courseNotes/src/main/scala/PatMatch.scala.

Multiway Branch/Match on Value

Scala has a multi-way branch, just like many other languages. Scala’s match expression is similar to a switch statement in other languages. You can match on any object, including numbers and Strings.

Because every Scala expression returns a value, the values returned by each of the cases of a match expression should be of the same type, or the Scala compiler will widen the return type of the entire match expression. We discussed this in the Type Hierarchy and Equality lecture. The cases are tested in the order written.

You can match on any object’s value or type

The matchOnValue methods accept a String parameter called x and returns an Int. The syntax for Scala 2 and Scala 3 differs: Scala 2 requires braces around the case expressions, which Scala 3 does not allow braces.

Scala 2 REPL
scala> def matchOnValue(x: String): Int =
  x match {
    case "a" => 1
    case _ => 0 // default case matches everything
  }
matchOnValue: (x: String)Int 
Scala 3 REPL
scala> def matchOnValue(x: String): Int =
  x match
    case "a" => 1
    case _ => 0 // default case matches everything
matchOnValue: (x: String)Int 

In both versions of the above code, the value of x is matched against the literal string "a", and if they are equal then the value returned by the match expression is the integer 1. Otherwise, the next match value, which is a wild card, specified with an underscore, matches every possible value and discards the matched value.

It is a good practice to ensure that every possible match is provided for. Wildcards are useful as catch-all clauses. As you already know, Scala uses underscores for many purposes, mostly as a wildcard or a placeholder. Let’s try this in the REPL; the method invocations are the same for Scala 2 and 3:

Scala REPL
scala> matchOnValue("a")
res0: Int = 1
scala>
matchOnValue("q") res2: Int = 0

The matchOnValue2 method uses a capturing variable called y instead of a variable called _. When written this way, y has no type or value constraints. The benefit of providing a variable name instead of an underscore is so the matched value can be referred to in the body of the case clause. I wrote a version for Scala 2 and a version for Scala 3:

Scala 2 REPL
scala> def matchOnValue2(x: String): Int =
  x match {
    case "a" => 1
    case y => if (y.isEmpty) 0 else y.charAt(0).toInt
  }
matchOnValue2: (x: String)Int 
Scala 3 REPL
scala> def matchOnValue2(x: String): Int =
  x match
    case "a" => 1
    case y => if (y.isEmpty) 0 else y.charAt(0).toInt
matchOnValue2: (x: String)Int 

The highlighted default case above matches every possible value. Instead of using an unnamed variable (_) as a wildcard, a variable named y is used as a wildcard. Notice that the body of the default case contains a conditional test based on the value of y. If y is the empty string, then the value 0 is returned, otherwise the numeric value of the first character in the string called y is returned. Let’s run this code:

Scala REPL
scala> matchOnValue2("x")
res3: Int = 120
scala>
matchOnValue2("a") res4: Int = 1
scala>
matchOnValue2("") res5: Int = 0

This is not the best writing style, and we will see a better way of writing this using a guard next.

You can run this code by typing:

Shell
$ sbt "runMain PatMatch1"
matchOnValue("a")=1
matchOnValue("q")=0
matchOnValue2("a")=1
matchOnValue2("q")=113 

Providing a Guard

Scala’s match expression can test against a condition with an if clause (notice that the test predicate is not enclosed in parentheses). This type of test predicate is often referred to as a guard.

Let’s rework matchOnValue2 so it uses a guard. The value of x is first compared to the literal string "a"; if there is a match, the integer value 1 is returned and the case clause is complete.

Otherwise, if y is the empty string, the value zero is returned and the case clause is complete. Finally, the catch-all clause has no restrictions; all values are unconditionally accepted by this clause, and since we know that y does not contain the empty string, the value of its first character is converted to an integer and that becomes the value of the entire match expression.

Scala code
def matchOnValue3(x: String): Int =
  x match {
    case "a"             => 1
    case y if y.isEmpty => 0
    case y               => y.charAt(0).toInt // this is the catch-all default case
  }

The results are the same as before with matchOnValue2. I think a writing style that uses guards is easier to read. That said, here is an even better way of writing this expression:

Scala code
def matchOnValue4(x: String): Int =
  x match {
    case "a" => 1
    case ""  => 0
    case y   => y.charAt(0).toInt // this is the catch-all default case
  }

You can run this code by typing.

Shell
$ sbt "runMain PatMatch2"

Matching Against Multiple Values

A match expression can match against multiple values at once. Simply separate each value with a vertical bar (think of a vertical bar as meaning ’or’). For this example, the second case will be activated if the value of x is 2, or 4 or 6.

Scala REPL
scala> def wat(x: Int): Unit = x match {
     |   case 1 => println("x=1")
     |   case 2 | 4 | 6 => println("2, 4 or 6")
     |   case _ => println("something else")
     | }
wat: (x: Int)Unit
scala>
wat(1) x=1
scala>
wat(2) 2, 4 or 6
scala>
wat(3) something else
scala>
wat(4) 2, 4 or 6

Matching On Type

Let’s consider how to match according to an expression’s type instead of its value; this goes beyond switch statements in other languages. To do this, let’s define a method called whatever that can return type Any.

It is actually written to either return an Int or a String, depending on whether the current system time has an odd or an even number of milliseconds when the whatever method was invoked. I explicitly declared the return type as Any, which is actually the type returned by the if expression in the body of the whatever method.

Each time you run the method there is a 50% chance of getting the integer value 1 returned and there is a 50% chance of the string "blah" being returned.

Scala REPL
scala> def whatever: Any = if (System.currentTimeMillis % 2 == 0) 1 else "blah"
whatever: Any
scala>
whatever match { | case a: Int => println("no") | case b: String => println(b) | } no

This match expression selects the appropriate case clause according to the type returned by the whatever method; this match expression does not consider values when selecting the matching case clause. If the returned type from whatever is an Int, the first case clause is matched, which causes the matched value to be stored into a new immutable variable called a, then "no" is printed and the returned value from println (which is Unit) is returned as the value of the entire match expression.

If the type returned by the whatever method was instead a String, the second case clause will be selected, which will cause the value of the matched String to be stored in a new immutable variable called b, and that value will be printed. Again, the returned value from the selected case clause and therefore the entire match expression is Unit.

Immutable variables a and b are local to their respective case clauses.

The above match expression does not match every possibility; the whatever method is declared to return Any, and according to that definition there are an infinite number of possible types that it might return. Attempting to match against any other type will cause a runtime error, as we shall see shortly.

Let’s see what happens if whatever returns a different type, such as a Boolean. Our first attempt at writing this code does not compile.

Scala code
false match {
  case a: Int    => println(s"Whatever: Int with value $a")
  case b: String => println(s"Whatever: String with value ’$b’")
}

The compiler error message for the above is: scrutinee is incompatible with pattern type, which is itself an inscrutible error message. It just means that the type being matched has no matching case clause. We can make this code compile by widening the type being compared by writing it this way.

Scala code
(false: Any) match {
  case a: Int if a<3 => println(s"$a is an integer less than 3")
  case b: Int        => println(s"$b is an integer greater or equal to 3")
}

However, when you run the above you will get scala.MatchError: false (of class java.lang.Boolean). This is because there was no case clause that specified a Boolean type, or the value false, or a default case.

If the case clause has no type specification then it matches every type. Similarly, if the case clause has no value specification then it will match all values. Thus, a case clause without a value specification or a type specification or a guard should be the last case to be matched, because it is guaranteed to successfully match everything. As we saw earlier, if the default case clause has a named variable then the value being matched will be stored into that variable, otherwise if the default case clause uses an underscore, then the matched value is not available to the body of the case clause.

Let’s provide a default case that handles unexpected match types by not specifying a match type or a match value:

Scala code
(false: Any) match {
  case a: Int    => println("no")
  case b: String => println(b)
  case c         => println(s"$c has an unexpected type") // default case matches on all values and all types
}

Providing a default case for match is important, unless we know that all possible cases have been provided. We will discuss sealed classes and traits in the Sealed Classes and Extractors lecture, which allows us to ensure that all possible cases have been considered. Let’s add a default case to the previous match on whatever’s return value:

Scala code
whatever match {
  case a: Int if a<3 => println(s"$a is an integer less than 3")
  case b: Int        => println(s"$b is an integer greater or equal to 3")
  case x             => println(s"$x is not an integer")
}

You can run this code by typing.

Shell
$ sbt "runMain PatMatch3"

Output might be something like the following, depending on the random values and types generated.

Output
whatever: String with value ’blah’
false is not an integer
1 is an integer less than 3

Example: UnWrapping Java nulls Wrapped with Option

We saw in the Option, Some and None lecture that we could use get, getOrElse and orElse to pull out the value that might be contained within an Option. You can also use match to extract a value from Option. As an example, the maybeSystemProperty method accepts the name of a system property and either returns a String containing the name and value of the system property, or a default message if the property is not defined.

Scala code
def maybeSystemProperty(name: String): String =
  Option(System.getProperty(name)) match {
    case Some(value) => s"Property ’$name’ value=’$value’" // value is extracted from the Option
    case None        => s"Property ’$name’ is not defined"
  }

The method works by wrapping the result of looking up the value of the given property name in an Option. If the system property is defined, its value is returned, wrapped in an instance of Some, otherwise if the system property is not defined, None is returned. This Option value (a Some instance or None) is then provided to the match. Two cases are provided for: the Some case and the None case.

The way the first case works may surprise you: a new immutable variable is created called value, and its scope is restricted to the first case. It does not need parentheses around it, but you could write them if that helps you understand. BTW, many Scala beginners actually write these parentheses, but they are unnecessary. If the Option being matched against is actually an instance of Some, its value is extracted and that becomes the value of the value variable.

The Unapply lecture will talk about how the extraction process works. If the Some case did not match, the value variable is discarded and the next case is considered. Because Option can only have one of two possible subtypes (Some or None), the None case is guaranteed to match if the Some case did not match. Notice that the None case has no variable name and no underscore. This is legal, and it means that the value of None is not captured.

This means that a match expression can do more than just match values and types: it can also extract values and process or compare them during or after the matching process.

Let’s try this out. As you can see, os.name is a predefined Java system property, and there is no system property called a.

Scala REPL
scala> maybeSystemProperty("os.name")
res3: String = Property ’os.name’ value=’Linux’
scala>
maybeSystemProperty("a") res4: String = Property ’a’ is not defined

You can run this code by typing.

Shell
$ sbt "runMain PatMatch4"
maybeSystemProperty("os.name")=Property ’os.name’ value=’Linux’
maybeSystemProperty("a")=Property ’a’ is not defined 

Matching On Type With a Guard

Let’s modify a previous example to use a guard in a match on type. Notice that the cases are ordered such that they are progressively less restrictive.

Scala code
whatever match {
  case x: Int if x<3 => println(s"$x is an integer less than 3")
  case x: Int        => println(s"$x is an integer greater or equal to 3")
  case x             => println(s"$x is not an integer")
}

We can package the above into a method called guardedMatch. Notice that guardedMatch accepts Any type.

Scala code
def guardedMatch(value: Any): String = value match {
  case x: Int if x<3 => s"$x is an integer less than 3"
  case x: Int        => s"$x is an integer greater or equal to 3"
  case _             => "Did not get an integer" // catch-all case
}

Let’s try it out:

Scala REPL
scala> guardedMatch(0)
res3: String = 0 is an integer less than 3
scala>
guardedMatch(99) res4: String = 99 is an integer greater or equal to 3
scala>
guardedMatch("blah") res5: String = Did not get an integer

You can run this code by typing.

Shell
$ sbt "runMain PatMatch5"
guardedTypeMatch(0)=0 is an integer less than 3
guardedTypeMatch(99)=99 is an integer greater or equal to 3
guardedTypeMatch("blah")=Did not get an integer 

Another Example

This example demonstrates guards with a match on type. We worked with the Animal, Frog and Dog classes in the earlier Type Hierarchy and Equality, Classes Part 1, Classes Part 2 and Case Classes lectures.

Scala code
abstract class Animal(numLegs: Int, breathesAir: Boolean) {
  private val breatheMsg = if (breathesAir) "" else " do not"
  val msg = s"I have $numLegs legs and I $breatheMsg breathe air"
}
case class Frog(canSwim: Boolean, numLegs: Int, breathesAir: Boolean) extends Animal(numLegs, breathesAir)
case class Dog(barksTooMuch: Boolean) extends Animal(4, true)

Now we can figure out what kind of Animal we have. match works by successively comparing the animal type against all the cases, in order. You can think of each case as a list of filters, from most specific to least specific. Notice that the first match case checks the Frog’s numLegs value; this class property is available only if the match on type was successful. Also notice that the last match (x) does not specify a value, type or guard – by now you should know that this means that this case will always match and so acts as the default case.

Scala code
def classify(animal: Animal): String = animal match {
  case frog: Frog if frog.numLegs>0 =>
    s"Got a Frog with ${frog.numLegs} legs; canSwim=${frog.canSwim} and breathesAir=${frog.breathesAir}"
case tadpole: Frog => s"Got a tadpole without legs; breathesAir=${tadpole.breathesAir}"
case dog: Dog if dog.barksTooMuch => s"Got a Dog that barks too much"
case dog: Dog => s"Got a quiet Dog"
case x => s"Got an unexpected animal $x" }

The classify method simply prints the appropriate message. Let’s try it out.

Scala REPL
scala> val frog1 = Frog(canSwim=true, 4, breathesAir=true)
frog1: Frog = Frog(true,4,true)
scala>
classify(frog1) res1: String = Got a Frog with 4 legs; canSwim=true and breathesAir=true
scala>
val tadpole = Frog(canSwim=true, 0, breathesAir=false) tadpole: Frog = Frog(true,0,false)
scala>
classify(tadpole) res2: String = Got a Frog with 0 legs; canSwim=true and breathesAir=false
scala>
val bigDog = Dog(barksTooMuch=false) bigDog: Dog = Dog(false)
scala>
classify(bigDog) res3: String = Got a Dog and barksTooMuch=false
scala>
val yapper = Dog(barksTooMuch=true) yapper: Dog = Dog(true)
scala>
classify(yapper) res4: String = Got a Dog and barksTooMuch=true

You can run this code by typing:

Shell
$ sbt "runMain PatMatch6"
classify(frog1)=Got a Frog with 4 legs; canSwim=true and breathesAir=true
classify(tadpole)=Got a tadpole without legs; breathesAir=false
classify(bigDog)=Got a quiet Dog
classify(yapper)=Got a Dog that barks too much 

Order Matters

The order of case expressions matters for performance. For example, if one case appears 90% of the time it should precede others.

Exercise

Given an array of various types of values.

  1. Match against each type.
  2. Each match case should return a Tuple2[Any, String], containing the matched value and a String that states the matched value and the matching type. We discussed tuples in the Scala Tuples lecture earlier in this course.
  3. Print out the string

Hints:

  1. You can declare an Array of Any like this:
    Scala code
    Array("blah", 1, 1.0)
  2. You can loop through an array like this:
    Scala code
    Array("blah", 1, 1.0) foreach { valueToMatch => println(s"valueToMatch=$valueToMatch") }
  3. You can use the Java getClass.getName method to obtain the fully qualified name of an object’s class.

Solution

Scala code
package solutions
   object PatMatch101a extends App {
    Array("blah", 1, 1.0) foreach { valueToMatch =>
      val matchResult: (Any, String) = valueToMatch match {
        case string: String =>
          (string, s"Got a String: $string")
         case int: Int =>
          (int, s"Got an Int: $int")
         case double: Double =>
          (double, s"Got a Double: $double")
      }
      println(s"matchResult=${matchResult._2} and is of type " +
              "${matchResult._1.getClass.getName}")
    }
  }

The code for this solution is provided in courseNotes/src/main/scala/solutions/PatMatch101a.scala.

You can run it like this:

Shell
$ sbt "runMain solutions.PatMatch101a"

* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.