Scala and pattern matching

Versions: Scala 2.12.1

At first glance Scala's pattern matching looks similar to Java's switch statement. But it's only the first impression because after analyzing the differences we end up with some smarter idea.

New ebook 🔥

Learn 84 ways to solve common data engineering problems with cloud services.

👉 I want my Early Access edition

In this another post of One Scala Feature per week series we'll focus on the feature called pattern matching. In the first section we'll discover its definition. In the next section we will see its use cases. Finally, in the last part we will read about some reflex we often have in the first months of working with Scala.

Pattern matching definition

Pattern matching is a mechanism letting us to check some value against one or more defined pattern. The similarity with switch statement comes mainly from the grammar that looks like: matchedValue match { case SomeTypeToMatch => "xxx" case SomeOtherTypeToMatch => "yyy" case _ => "default value" }

As you can see, pattern matching is built of the matched value (matchedValue) and one or more cases against which the value is tested (case ... => "..."). It's a simple version of pattern matching. More advanced one uses the system of pattern guards that help to limit the conditions under which given case matches. The following snippet shows the use of guards used to detect if given number is even or odd, or if matched value is not an Int:

it("should apply with guards") {
  // Please note it's only for illustration purpose
  // Obviously, we could avoid an IllegalArgumentException by simply
  // transforming Any argument to an Int
  def getNumberLabel(nr: Any): String = {
    nr match {
      case number: Int if number % 2 == 0 => "even"
      case number: Int if number % 2 != 0 => "odd"
      case _ => throw new IllegalArgumentException("Test can be made only on integers")
    }
  }

  val labelFor2 = getNumberLabel(2)
  labelFor2 shouldEqual "even"
  val labelFor3 = getNumberLabel(3)
  labelFor3 shouldEqual "odd"
  val error = intercept[IllegalArgumentException] {
    getNumberLabel("text")
  }
  error.getMessage shouldEqual "Test can be made only on integers"
}

The above code snippet shows the use of guards that in fact are simple if statements applied on matched elements.

Pattern matching use cases

Pattern matching with guards looks like a switch statement after all. However it's not the case since it can do a lot more stuff, as matching only types, recursively match a nested data structure and so on. All interesting use cases, found in already quoted in the post about "Pattern matching in Scala", are listed below:

Risk of overuse

But the risk of so powerful pattern matching is its overuse. It's especially visible in the code of programmers using this mechanic for the first time. In such situation almost all if-else conditions are expressed as pattern matching cases. Is it bad ? Yes and not. For many pattern matching is more readable than if-else statement. For the others its use should be only limited to specific cases (e.g. extracting components from objects) and not to make conditionals checks on the same type values (after all guards contain an if too).

A good point helping to make a decision concerns code complexity. In the compiled bytecode, a simple if-else statement is much shorter than pattern matching even applied on only 2 branches:

def getOneOrAnotherIf(value: Int): Boolean = {
  if (value%2 == 0) true else false
}

def getOneOrAnotherPatternMatching(value: Int): Boolean = {
  value match {
    case nr if nr%2 == 0 => true
    case _ => false
  }
}

That in bytecode gives:

  public boolean getOneOrAnotherIf(int);
    Code:
       0: iload_1
       1: iconst_2
       2: irem
       3: iconst_0
       4: if_icmpne     11
       7: iconst_1
       8: goto          12
      11: iconst_0
      12: ireturn

  public boolean getOneOrAnotherPatternMatching(int);
    Code:
       0: iload_1
       1: istore_2
       2: iload_2
       3: lookupswitch  { // 0
               default: 12
          }
      12: iload_2
      13: iconst_2
      14: irem
      15: iconst_0
      16: if_icmpne     23
      19: iconst_1
      20: goto          24
      23: iconst_0
      24: ireturn

This section is not here to give you the final answer but only to emphasize the risk of pattern matching overuse - especially at the beginning. With the practice we should acquire the reflex about the cases when if-else is more adequate than pattern matching and inversely.

The introduction talked about similarities witch Java's switch case, at least at first approach. But as proven throughout all 3 sections, it's only superficial. Java's switch statement applies on specific values whereas Scala's pattern matching has much more use cases. They all were shown in the second section and among the most important ones we should list 3 kinds of matches: deep, type and sequence ones. Combined together they can help to build really powerful methods extracting some values of matched object.