Scala pattern matching & lowercase case objects
Posted on Wed 30 November 2016 in Coding
I just learned something about pattern matching and case objects in Scala which I thought I’d share. Consider the following code:
object Messages {
case object foo
case object bar
}
import Messages._
def identify(msg: AnyRef) = msg match {
case foo => "foo"
case bar => "bar"
case _ => "unknown"
}
identify(bar)
What does it print?
If your answer is “bar”, you’re wrong - the correct answer is “foo”. But why is that?
The problem here is that the case objects (in particular foo
, which comes first in the pattern matching block)
have names that start with a lowercase letter.
When the Scala compiler comes
across the pattern matching block, section 8.1.1 from the Scala Language Specification
(SLS) kicks in and tells the
compiler that foo
and bar
are variable patterns:
8.1.1 Variable Patterns
[…]
A variable pattern x is a simple identifier which starts with a lower case letter. It matches any value, and binds the variable name to that value. […]
(Note the bold part–trying to use a variable name that starts with an uppercase letter in pattern matching results in a compile error.) Luckily, the compiler is extremely helpful in describing the problem and suggesting a solution:
example.scala:8: warning: patterns after a variable pattern cannot match (SLS 8.1.1)
If you intended to match against object foo in package <empty>, you must use backticks, like: case `foo` =>
My excuse? Uh, compiler warnings flash by quite quickly in my editor and are typically replaced with the result of a unit test run or something similar… :-) But I also (vividly) recall thinking something along the lines of “surely the compiler is smart enough to know that I intend to match against the case objects.” In retrospect, having a compiler that guesses at my intentions rather than following a spec seems like a Bad Thing™!
Thus:
def identify(msg: AnyRef) = msg match {
case `foo` => "foo"
case `bar` => "bar"
case _ => "unknown"
}
Note that the situation doesn’t apply when pattern-matching a lowercase-named case class, since that is interpreted as constructor pattern matching (SLS 8.1.6).
If you for some reason don’t want to use backticks, you can also qualify the case object names:
def identify(msg: AnyRef) = msg match {
case Messages.foo => "foo"
case Messages.bar => "bar"
case _ => "unknown"
}
Or just avoid lowercase case object names altogether!