Friday, August 30, 2013

Def and Val in Scala constructors

TL;DR - use def all the time, and things just work.

Remember that old adage from Java, "Never call a nonfinal method in your constructor"?
Yeah, that still applies in Scala, and it keeps biting me. It's easier to get wrong in Scala.

Here's a refresher:
When a class is instantiated, the superclass is initialized first. Its fields are initialized and its constructor executed. If that constructor calls a method, and that method is overridden in a subclass, and the subclass's version accesses a field that exists in the subclass, then FAILURE: that subclass field has not been initialized. It is null.

Here's what that looks like in Scala:
The initialization of one val (secondsOfWaiting) uses a val that is overridden in the subclass. Try to instantiate a OneSpecificTest, and you get an NPE.[1]
This doesn't happen in Java because you can't override fields.

One way of fixing this is to use def instead of val on the overridden property. A def in the subclass won't be initialized to null. To use def in the subclass we have to use def in the parent class too. It looks like this:
This works because the def in the subclass references only a field in the superclass that has already been initialized. That's coincidence. Reorder the lines in the superclass, and the NPE is back:
If the subclass referenced its own val, that would also NPE. The only safe vals are defined in the superclass before any initialization references the subclass method. In other words, not safe at all!
This doesn't happen much in Java because we declare the fields at the top, and the constructors are contained. In Java, methods and fields are very different; we notice calling a method, and notice its modifiers.

Scala deliberately blurs def and val. Override a def with a val, switch between a val and a def, you don't affect the interface. Scala says, don't care whether a property is a def or a val under the hood. Yet the difference is significant when you implement a class.
In Java, I don't write a block to initialize a field. In Scala, I do; it's another way def and val are easy to interchange. Easy, but not always wise.

The right solution to this initialization problem: make secondsOfWaiting a def instead of a val. Access to longEnough takes place after construction.

lazy val secondsOfWaiting works too; that also delays evaluation.

Initialization order is complicated. It looks easy when everything else is stripped out, but when the class definition is longer, it is not obvious which blocks of code are running at construction and which later. I don't want to think about this!

Can we make it simpler?

Universal solution: use def for everything. When everything is an operation, initialization is not an issue. Order is not an issue. We are free from worry about what happens first.

From now on I'm writing all my properties with def, or function-type vals.
I'll let you know how that goes.

----------------
[1] In Scalatest, the NPE on initialization gets wrapped in an error about being unable to load a test that was found - it's a little cryptic. Next time I see it at work I'll paste the exact one in here and then Google will like me.

[2] But what about performance?? I'm ignoring performance. Premature optimization is the root of all evil, and all optimization is premature until you prove that it isn't.
If you want memoization, lazy val is as good as def. That adds some overhead compared to a regular val, but less than a def if it is called many times. But still, this idea of making everything an operation on the JVM is probably a disaster for performance. That doesn't make it wrong, conceptually.

3 comments:

  1. Nice article. I finally decided to sit down and figure this one out for myself just earlier this week and with a bit of creative googling discovered

    "early definitions" and "constant value definitions"

    https://github.com/paulp/scala-faq/wiki/Initialization-Order

    Your example would need a little re-factoring (arguably this would be a good thing) but you could avoid using defs for constants (a good thing IMO - as the intent is clearer).

    ReplyDelete
  2. Or, in summary:
    http://memecrunch.com/meme/9M0S/lazy-val-all-the-things/image.png

    ReplyDelete
  3. mmm I'm not getting any NPE with something equivalent (I think) to what you posted; maybe I'm missing something here

    ``` scala
    class GeneralThing {

    final val oneMinute = 1
    val longEnough = 30
    final val secondsOfWaiting = longEnough + 12
    }

    class ConcreteThing extends GeneralThing {

    override val longEnough = oneMinute * 5
    }

    object NPE {

    def main(args: Array[String]): Unit = {

    val npe = new ConcreteThing()
    println(npe.toString)
    }
    }
    ```

    ReplyDelete