Last active
May 29, 2018 13:52
-
-
Save overthink/e6a4a9264110190b7e00f1a3880599f1 to your computer and use it in GitHub Desktop.
Demo of why vals in traits are "bad" (aka you have to be very careful)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The issue is that the superclass gets instantiated first, and if it has an eager val that | |
calls an abstract def (or abstract lazy val, if such a thing is possible) it will capture | |
the not-yet-fully-initialized value of that val in the subclass (which is usually null). | |
scala> trait T { def f: String; val callsF = f } // *val* `callsF` calls abstract def `f` -- risky! | |
defined trait T | |
scala> object X extends T { val f = "foooo" } // `f` is implemented with a strict val -- bomb is set | |
defined object X | |
scala> X.callsF | |
res0: String = null // unexpectedly, X.callsF returns null instead of "foooo" | |
scala> object Y extends T { def f = "baaar" } // implement `f` with a def -- all good | |
defined object Y | |
scala> Y.callsF | |
res2: String = baaar // as we'd hope | |
So you either have an out-of-band agreement that all subclasses must implement the | |
trait with only `def`s, or you only use defs (and lazy vals if you must) in your trait, | |
and the implementors are free to do whatever. I like the latter option personally. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment