3
votes

I was working with scala and did something like this:

trait Foo { val a: String }
trait Foo2 extends Foo { val a = "foo" }
trait Bar extends Foo { val b = a + "-bar" }
object Bar2 extends Bar with Foo2

I expected that Bar2.b would be "foo-bar", but it ends up being "null-bar". Bar2.a is "foo" as expected.

If I change from val to def for definitions of 'a' like this:

trait Foo { def a: String }
trait Foo2 extends Foo { val a = "foo" }
trait Bar extends Foo { val b = a + "-bar" }
object Bar2 extends Bar with Foo2

Bar2.b is actually "foo-bar".

In example 2, the def 'a' from Foo2 that implements the abstract one in Foo is put into the definition of Bar2 before the val 'b' is evaluated. In example 1, the val 'a' from Foo2 overrides the val 'a' from Foo after val 'b' is defined in Bar/Bar2.

Why is this the case? Why do val and def implement abstract methods at different times?

2

2 Answers

4
votes

You always want to use either def or lazy val in traits (rather than just val) to avoid the awkward behavior you discovered.

As for why, which gets to the low-level details of Scala's implementation in the JVM, check out Josh Suereth's excellent talk "Binary Resilience" from Scala Days 2012.

3
votes

It's because there is Java behind. Initialized (non abstract) vals are translated to public getters with private fields and they are initialized in certain order. Abstract vals are just getters. Check Java documentation on this. You may also see early initializers feature of Scala to find a better solution: In Scala, what is an "early initializer"?

And also order of mixing-in matters. This should give you the right result:

object Bar2 extends Foo2 with Bar