Pattern Matching in Scala
Not so long ago I gave a presentation about Scala and ScalaTest to my co-workers. This was just basics as I wanted to give them a good and solid base for future researching this language. As it turned out - I probably learned a lot more than they did from this :)
Whenever there is Scala mentioned there also is the phrase pattern matching. Of course Scala does support pattern matching but I didn’t know how much fun one can have with this. In this post I’d like to show a few fun things we can do with this.
Simple matching
Let’s start with something simple:
def showcase(input: Int) = input match {
case 1 => "One"
case 2 => "Two"
case _ => "Other!"
}
This is the simplest case - we have value of some concrete type and we want to execute some code
depending on that value. This can be easily replaced with Java’s switch
instruction or bunch of
if-else
instructions.
If you’re new to Scala then you can be wondering what means the case _
- this will match any other
value than listed above it. In fact it is important that this is at the very bottom case
because
values are matched from top. If the value didn’t match the first case
then next is checked. If the
value doesn’t match any case then exception is thrown. Also - function that is not covering all
possible inputs is called partial function.
OK, basic things are simple, but it’s not that we couldn’t write something like this easily in Java. Let us try something a bit different:
sealed abstract class Base
class First extends Base
class Second extends Base
def showcase(input: Base) = input match {
case f: First => "First"
case s: Second => "Second"
}
First of all - we declare the Base class. sealed
keyword means that all direct subclasses of
this class are contained in the same file. Then we declare subclasses First
and Second
.
Having such class structure we can match the class of given input
. This is of course something we
CAN do in Java but it’s not that elegant. We would have to write bunch of isInstanceOf
and then
cast our input to target class. Casting means that we can mix something up and generate some casting
exceptions. Here - everything is taken care of and is type safe.
You may have noticed that I’ve not specified the case _
. You might think that this means that the
showcase
function is partial function but it is not. In fact by using keyword sealed
I made
sure that I know EVERY direct implementation of Base
class. So I know that input can only be
either of type First
or Second
. There is just no other option!
Speaking of options - exactly this trick is used with Option[T]
type. It has two implementations -
Some()
and None
. There is no third option.
Matching case classes
Assume that we have declared following class:
case class Post(id: Int, title: String, content: String)
Defining class as case class
gives us some benefits for pattern matching:
def showcase(post: Post) = post match {
case Post(1, _, _) => "post with id = 1"
case Post(_, title, _) => title
}
As you can see it’s quite different than matching the type. We get some Post
as the input and then
test it with our matches.
On the second line we check if given post has id == 1
and if it does then returns some descriptive
text. Note that we can tell the compiler that we don’t really care for other parameters with _
.
If the post’s id
is not 1 then match it’s title to variable named title
and return that
variable. Once again - we ignore inputs that we are not interested in.
But what if we want to check only certain parameter values and then have the whole object to play with? Don’t worry - Scala got you covered!
def showcase(input: Post) = input match {
case post@Post(1, _, _) => post.content
}
Above example shows that we can match the value of id
and if the value matches 1 we want the whole
object to be assigned to variable post
. We can later use this variable as a reference to our
matching object. In this case we just return this post’s content.
Matching regular expressions
You can’t say matching without saying regular expressions, can you? Let’s consider this simple expression for matching dates:
val date = """(\d\d\d\d)-(\d\d)-(\d\d)""".r
Having that you can already match any string you want with:
"2015-04-05" match {
case date(year, month, day) => s"It's year $year"
}
As you can see we use the variable name followed parentheses with all the matched groups from
regular expressions. Then we can use this groups as normal variables. In this case we just want to
match a year so maybe month
and day
could be replaced with _
? Sure!
"2015-04-05" match {
case date(year, _*) => s"It's year $year"
}
Whoah! What happend here? Well… as we are not interested in all remaining groups we can ignore
them all with this simple notation _*
. In this case it’s not that different but if you had ten
matching groups you’d have to ignore them all explicitly. This way you can ignore them all together!
Matching in for
loops
Last thing I’d like to show you is that you can easily loop through lists of things that matches your criteria:
val posts = List(
Post(1, "Title 1", "Content 1"),
Post(2, "Title 2", "Content 2"),
Post(3, "Other Title", "Content 3"))
for( p @ Post(1, _*) <- posts) {
// ...
}
This for
loop will only execute it’s body for the first post from the list. In this case we can
match only one value because the 1 is passed statically. What if we could combine different matching
styles to tell the compiler what we desire? Totally doable!
val pattern = "^Title.*".r
for( p @ Post(_, pattern(), _) <- posts) {
// ...
}
What do we have here? We added simple regex pattern to match only titles that start with “Title”.
Please note the parentheses after regex variable name. We haven’t specified any names for matched
groups because our regular expression doesn’t capture any groups. Empty parentheses are still
required. Otherwise scala would think that we want to catch the title and create varaible called
pattern
with value of the whole title. Adding parentheses tell the compiler that this is regular
expression and that it should be used for matching.
Summary
Pattern matching in Scala is a powerful tool. I’m certain that what is describe here is just the tip
of an iceberg, but I hope it gives you some useful tips for working with match
keyword.
See you next time!