Saturday, March 3, 2012

Scala : On constructors and companion objects

With companion objects, you can utilize the apply method to generate constructors that look like case classes.  This might not seem like much, but it's a nicety that feels idiomatic to me at this point.  If you're new to Scala, this is something that I think is worth a moment of your time.  It is helpful not only in taking about constructors and companion objects, but most importantly the apply method.  The apply method isn't something I really had a feel for until quite recently, and it's such a powerful mechanism that I think it really should be Scala 101.  It's a difficult concept to work with when you're still very much in the Java mindset.  I think there are aspects of Scala like this that fit in the mind of a C++ programmer more easily than a Java programmer.  Let's have a look at some code.


import io.Source

class Thingy(name: String, info: String, saveFile: File) {
  val fw = new FileWriter(saveFile)
  fw.write(info)
  fw.close()

  def this(name: String, info: String) = this(name, info, "data/"+name.filter(Character.isLetter(_)))
}

object Thingy {
  def apply(name: String, info: String) = new Thingy(name, info)
  def apply(name: String, info: String, File: saveFile) = new Thingy(name, info, saveFile)
  def apply(name: String) = {
    val file = new File("data/"+name.filter(Character.isLetter(_)))
    new Thingy(name, Source.fromFile(file).mkString, file)
  }
}


The Thingy class is used to represent a simple data object that correlates to a file.  This code isn't DRY and it could stand some improvements, but it's a simple example that works for the purposes of a demonstration.

There are two things here that both kind of look like Java classes.  One is labelled with the object keyword and the other with the class keyword.  In simple terms, the object is like an class definition that contains the methods that would have been declared static in a Java object.  It's a bit more than this, but I'm not gonna delve into that here.  The one that is labelled with the class keyword is more like a typical Java object.  It has instances and maintains state (sort of).  The definition that is labelled "object" is called a companion object in Scala.

Initially many Java programmers, myself included, look at this and ask why the heck would you want to do that?  Seems a bit clunky perhaps.  Then you come across the apply method.  This one use case for me, helped give the object type a specific useful meaning beyond just static methods.

The apply method is a special marker in Scala.  Let's look at a simple case, a List:

scala> val k = List("one","two","three")
k: List[java.lang.String] = List(one, two, three)

scala> k(2)
res1: java.lang.String = three

This uses the scala REPL which can be accesses just by typing "scala" (assuming you have the scala executable in your path).

The call k(2) is just like k[2] in Java.  In this case we can see that it's just like an array index operator.  The trick is that it's actually calling the apply() method on the class (not the object here, but the usage is similar).  The interesting thing is this means for a custom object, like our Thingy, we can define what behavior we want when we call an object in this fashion.  In our case, we have three apply methods.  Two that create an instance, and one that acts a bit like an array index.

Now when we call Thingy("file_I_want_to_read.txt") I get back a Thingy that has been fully initialized from the file just like I might call something like
new Thingy(new File("data/"+name.filter(Character.isLetter(_))).

In Java, we cannot execute any code in a constructor prior to calling the super() method.  This is pretty annoying at times, and we end up making work-arounds for this by using the factory pattern.  The factory pattern is just fine, but I feel like it's overkill for something this simple, and Scala has a handy way of doing this that is pretty similar to the factory pattern, but syntactically much nicer*.

The object gives us the power to do stuff before we perform the initialization of our object, and therefore makes this very easy.

You may have noticed that the "new" keyword went away in there.  Using the apply method on the companion object, we have created what more or less appears to be what a static constructor might look like in Java, or like I mentioned, a short-cut for a Factory.  It might be more convenience than anything, but it makes our code look better, removing the ugly details of constructor somersaults from the object itself and removing the littering of "new" calls throughout our code.  Because it's a separate method call, it is not limited to returning an object of the same type as it's namesake.  This has interesting implications/applications for dependency injection and cache management.

* It it easy to dismiss much of the niceties of things like Scala and Groovy as "syntactic sugar", and whilst that's true in some cases (not so much in others), sugar can sure make my coffee taste a whole lot better.  Syntactic short-cuts like this can make your code both more readable and less bug prone, so give it a go before you dismiss features as "syntactic sugar".

2 comments:

  1. So where is the difference to a Java class Thingy which just reads the file? Since it is your own class you don't have to call super().

    Frank

    ReplyDelete
  2. You should make the constructor of the the Thingy class private. Then users are forced to use the companion object factory.

    ReplyDelete