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 = threeThis 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".
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().
ReplyDeleteFrank
You should make the constructor of the the Thingy class private. Then users are forced to use the companion object factory.
ReplyDeleteA programming language could be considered syntactic sugar of assembler/binary, which could be considered syntactic sugar of electric voltages.
ReplyDelete