In Scala, a val can override a def, but a def cannot override a val.
So, is there an advantage to declaring a trait e.g. like this:
trait Resource {
val id: String
}
rather than this?
trait Resource {
def id: String
}
The follow-up question is: how does the compiler treat calling vals and defs differently in practice and what kind of optimizations does it actually do with vals? The compiler insists on the fact that vals are stable — what does in mean in practice for the compiler? Suppose the subclass is actually implementing id with a val. Is there a penalty for having it specified as a def in the trait?
If my code itself does not require stability of the id member, can it be considered good practice to always use defs in these cases and to switch to vals only when a performance bottleneck has been identified here — however unlikely this may be?
Short answer:
As far as I can tell, the values are always accessed through the accessor method. Using
defdefines a simple method, which returns the value. Usingvaldefines a private [*] final field, with an accessor method. So in terms of access, there is very little difference between the two. The difference is conceptual,defgets reevaluated each time, andvalis only evaluated once. This can obviously have an impact on performance.[*] Java private
Long answer:
Let’s take the following example:
The
ResourceDef&ResourceValproduce the same code, ignoring initializers:For the subsidiary classes produced (which contain the implementation of the methods), the
ResourceDefproduces is as you would expect, noting that the method is static:and for the val, we simply call the initialiser in the containing class
When we start extending:
Where we override, we get a method in the class which just does what you expect. The def is simple method:
and the val defines a private field and accessor method:
Note that even
foobar()doesn’t use the fieldid, but uses the accessor method.And finally, if we don’t override, then we get a method which calls the static method in the trait auxiliary class:
I’ve cut out the constructors in these examples.
So, the accessor method is always used. I assume this is to avoid complications when extending multiple traits which could implement the same methods. It gets complicated really quickly.
Even longer answer:
Josh Suereth did a very interesting talk on Binary Resilience at Scala Days 2012, which covers the background to this question. The abstract for this is: