Introduction to pattern matching in Scala
Pattern matching is a quite commonly used “programming pattern” in functional languages, because it fits nicely into the “functional way of thinking”, and it is quite powerful and handy as a tool.
It’s somewhat similar to a sequence of if / else statements or to the switch statement of Java or C, but it is much more powerful, because it lets you form more complex conditions for matching, even for the inside of an object.
In some cases Scala can automatically decompose an object that you want to match and look inside it to check if its structure matches your expectations.
But, before going too deep, let’s see some examples.
Example 1: matching value
Here the object what we match to patterns is x, an integer.
object MatchTest1 extends App {
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "many"
}
println(matchTest(3))
}
One important thing to note is the last case where an underscore is used: this is Scala’s notation for “match anything”. Without this line, we would get a “scala.MatchError” because we called the matchTest() function with the value 3, which is not covered by the other case branches.
So the main point to remember from here is that you should be sure to cover all possible options when using a match expression if you want to avoid ugly errors. The fail-safe option usually is to use the underscore as the last case. It will define a default path for the program flow for “all other cases”.
(Note: I just copied this example from the official documentation).
Example 2: matching type
If we want to write a function that behaves differently for different input types, we can write something like:
def getType(x: Any): String = {
x match {
case x: String => "String"
case x: Integer => "Int"
case _ => "I don't know"
}
}
Note 1: “Any” is a general type in Scala, similar to the “Object” of Java, which is an ancestor of all other types.
Note 2: You don’t need to use the same value name in the cases, because each case has its own scope with regards of the incoming object to be matched. Just be sure to use the same name on both sides of the arrow within a case line.
def getType2(x: Any): String = {
x match {
case a: String => "String: " + a
case b: Integer => "Int: " + b
case _ => "I don't know"
}
}
Example 3: using guards
Let’s say we need some extra conditions to be true before applying the right side of a case. Scala lets us perform these additional checks within any of the cases independently, as in the following example:
def getType3(x: Any): String = {
x match {
case a: String if a.startsWith("He") => "String with 'He'"
case b: Integer if b > 0 => "Positive integer"
case _ => "I don't know"
}
}
Example 4: matching structure
I promised in the first paragraph that in some cases we will be able to “automatically decompose an object” to look inside it and check if its structure matches our expectations.
How does it work in Scala, and what are the prerequisites?
The answer is: case classes! In one of my following posts I will cover them in more detail. For now let’s think about a case class as a composite object with some auto-magic: automatically generated constructors and getters, for example.
For the more curious, here are some nice and short explanations, and the official (quite dense) description of case classes.
So, for an example of automatic decomposition, at first we need a case class. We’ll write one, representing a tweet message, that has three parts:
- an identifier (id: Long)
- an author (user: User, that itself could be a complex case class, now it only contains one String, a name)
- a message (msg: String)
Defining these new types (User and TweetMsg) in Scala takes only two fairly short lines:
case class User(name: String)
case class TweetMsg(id: Long, user: User, msg: String)
And now a function can match a tweet’s inners like this:
def processTweet(tweet: TweetMsg) = tweet match {
case TweetMsg(id, _, _) if id < 0 => "Invalid (negative) tweet id!"
case TweetMsg(_, user, _) if user.name == "adorster" => "Hello, " + user.name + "!"
case _ => "Just another tweet"
}
Example codes with tests are available at my scala-examples github repo.
Note n+1 : Until I set up a comments section somehow here, feedback is welcome via Twitter :)