AdvancedScalaObjects
The Gist
The tour doesn’t include this detail on what Scala objects are used for.
My Interpretation
You may recall that creating a case class automatically creates a singleton object of the same name that can be used to create and “de-create” (in a case
statement) instances of that class.
This ties together the two main purposes of a singleton object: as a factory and as an extractor.
Factory
Suppose we want to be able to create a Person
object from either a known first name and last name, or a “friendly” string that takes a full name. In Java, you might create multiple constructors. While you can also do so in Scala, multiple constructors are a bit cumbersome, syntax-wise, so a factory is a bit easier (as an aside, Ruby does not allow multiple constructors, so you would need this pattern there as well).
Note that this is almost entirely convention. The only real compiler “magic” that happens is in Scala’s special treatment of the apply()
method. Given a reference xxx
, Scala treats xxx(yyy)
the same as xxx.apply(yyy)
. In this case, the object Person
has two apply
methods, each returning an instance of Person
.
We could define a class Cat
that extends Person
(since all cats seem to think they are people), who gets his last name from his owner’s last name, and modify our Person
object to render Cat
objects sometimes:
Extractor
The more powerful use of objects is via a companion method to apply
, called unapply
, which Scala treats specially in a case
statement. We saw in CaseClasses that you can “extract” the elements of an object as part of a case statement. In those examples, we created case classes using the case
keyword. This tells Scala to automatically create some canonical structures for us. We could create those ourselves, however.
Suppose we extend our Person
class to have an optional middle name:
class Person(val last:String, val first:String, val middle:String)
Now, suppose we wanted to match on people without a middle name:
person match { case Person(last,first,middle) => println("has middle name") case PersonNoMiddle(last,first) => println("no middle name") }
With ordinary case
classes, we cannot do this. But it is possible by understanding how case
classes work. The expression between case
and =>
in the code above is telling Scala to call the unapply
method of the Person
object (or of the PersonNoMiddle
object).
When Scala encounters the first case
statement, it reads it as “if there is a method apply
of object Person
that takes a object of type Person
and returns an Option[Tuple3[String,String,String]]
, call that method and if the Option
it returns is not None
, set last
, first
, and middle
to the tuple’s values and execute the code to the right of the =>
. Otherwise, proceed to the next case
statement and start over”.
That’s quite a mouthful. And it may take a second to sink in, but this is what’s going on under the covers of Scala’s powerful case
classes and PatternMatching .
Fortunately, you don’t need to implement a lot of unapply
methods; rather you take advantage of the case
keyword creating them for you.
My Interpretation of This Feature
While this is a pretty advanced concept that might smack of a bit too much “magic”, it is actually quite logical and, more importantly, highly useful in aid of readable code.
It was hard to come up with succinct example that wasn’t artificial, but this is more of a “how does this work” type of concept than a “you need to know how to do this regularly”.
All that being said, I’m not sure why this feature couldn’t have been more formally tied to the concept (and definition) of a class.
I really like how in languages like Ruby or Objective-C, a symbol like Person
is a constant that refers to the object representing the Person
class. It just feels more OO to me. —Here, we have an object called Person
that just happens to be related to the class Person
through convention only. Perhaps this is fallout from the JVM’s internals?— In fact, an object named Person
has the same access and privs to the Person
class as objects of that class would.