Scala Tutorials Part #23 - Pattern Matching in Scala
Originally Posted On : 05 Oct 2017
This is part 23 of the Scala tutorial series. Check here for the full series.
- Pattern matching value types
- Using conditionals
- Pattern matching strings
- Capturing values
- Matching with Options
- Heterogeneous pattern matching
- Decomposing types using pattern matching
- Case class matching
- Pattern matching decompiled
We saw how extractors can aid pattern matching by writing an
unapply method. In this article,
we are going to see how they actually work and also look at the internals.
Let’s say we have an Integer variable and want to do a match on it.
Code is pretty self explanatory, it checks for matching values 0/1 and prints appropriate messages. The
_ is used to match any other value,
kind of like the
default in java switch statements.
That was straightforward, let’s try with a double.
It is more or less similar to the
Int example. In a real world situation we will have the need to use
conditional expressions to match one or more cases.
The left side of the expression is used to capture the variable and also the condition matching it while the right side i.e the expression after the
=> returns a value. In the above example it returns a
Unit since it just prints out and does not do any other computation.
If the execution does not match any of the cases, then it simply throws an exception.
Executing the above code results in the following.
Taking the same example above with a broader score range.
lowScore are actually doubles and can be used in the right side of the computation.
Since java switch case statements can take only constant values in its case’s, this is more elegant to work with. It is important to note that there is no need of a break statement since it automatically matches only of the cases present and falls back to the
_ case if there is no
match and throws an exception if there is no fallback as we saw above. Conditionals are also called guard statements similar to the guard in for comprehensions.
So far we have been seeing value types. Let’s take a look at string pattern matching which is very useful(Intentionally keeping the examples simple in order to grasp the concepts).
If we want the comparison to be case-insensitive then,
Locale should be handled correctly in the above example, but you get the idea.
We can optimize the above code block into something more concise as below,
| is a shorthand for or condition.
Each match in a pattern match block are capable of returning a value and hence the result can be stored into a variable.
In case we do not want to store the error message in the result, then we can use an
The results can then be pattern matched again as we saw in part 16.
Pattern matching is not restricted to a particular type. Because of Scala’s robust type system, we can literally match anything that fits the type hierarchy properly.
Another unique capability of pattern matching is to decompose an unknown type or higher type into a recognized type.
The type ascription
Any is necessary in order for the compiler to treat it as a higher type and avoid the variable type being automatically inferred to
In real world, the type you are going to match might come from an API endpoint/from a file etc.,
Case classes are named after pattern matching i.e the case keyword. They are naturally suited to it because of the
unapply method which gets automatically generated.
Let’s create a textbook example of cars.
Let’s create an instance of this car (the type ascription is important)
We can then do a match on this variable.
We do a pattern match on the case class type with a parameter name. The parameter
name is important since the case class cannot be created without it. You can revisit the decompiled version of the case class and see the
unapply method. It would make much more sense now and how it is useful in pattern matching. Case objects can also be matched using a
We are not going to exhaustively see how pattern matching works behind the scenes for all examples. Let’s take three different examples to understand how they behave. Since the decompiled code is pretty big, I have collected both in a gist.
Fernflower decompiler does a pretty good job of decompiling the code. It is interesting to see that in the first example it is being compiled to java switch statements, the second one with a bunch of if-else since switch case does not support condition based matching. The third one is done via a couple of
instanceOf comparisons. We can see that pattern matching gives us a very nice abstraction and lets the compiler deal with the all the hardwork. The decompiled code can change as the JVM evolves and a lot of features are added natively.
Let’s summarize what we have seen till now.
- An overview of pattern matching with value types and string
- Usage of pattern matching with case classes
- Type decomposition with pattern matching
There are more complex use cases of pattern matching in data structures such as
Vector etc., I will cover those when we get to collections.
We are also protected from quite a few run time issues partly due to the type system of Scala and also how pattern matching by itself is designed. Next time whenever you see a switch case/complex if-else structure, think of re-writing it with pattern matching.