4

I have the following code in scala:

trait A {
  val foo: String => Int = { in =>
    in.toInt
  }
}

trait B extends A {
  def bar(a: String) = {
    foo(a)
  }
}

class C(a: String) {
  self: B =>

  val b = bar(a)
}

val x = new C("34") with B

During instantiation of x I get NPE. Couldn't figure out why.

edit

NOTE: Can not figure out why foo of A trait doesn't get initialized

maks
  • 5,911
  • 17
  • 79
  • 123

3 Answers3

3

Please refer to the http://docs.scala-lang.org/tutorials/FAQ/initialization-order.html.

The only addition to this is that self-type makes class C abstract. So actually you do:

abstract class C(a: String) {
  def bar(a: String): Int
  val b = bar(a)
} 

val x = new C("34") with B

You can try to replace it in your code and see same result. Some more info here.

In short: the linearization of new C with B will be (B <- A) <- C, so initalization is C -> (A -> B). Please refer to Class Initialization section section:

After the superclass constructor is executed, the constructors for each mixin trait are executed. Since they are executed in right-to-left order within the linearization, but the linearization is created by reversing the order of the traits, this means the constructors for the mixin traits are executed in the order that they appear in the declaration for the class. Remember, however, that when mixins share hierarchy, the order of execution may not be quite the same as how the mixins appear in the declaration.

In your case new C("34") with B equals to class K extends C("34") with B; new K. Note that self type of class C doesn't affect the initialization order.

Simplified example:

scala> trait C {def aa: String; println(s"C:$aa")}
defined trait C

scala> trait D {val aa = "aa"; println(s"D:$aa")}
defined trait D

scala> new C with D
C:null
D:aa
res19: C with D = $anon$1@2b740b6

Solution: If your foo is placed in third party library (so you can't make it lazy), you may just use mix-in instead of self-type or at least mix A into the C class:

trait A {
  val foo: String => Int = { in =>
    in.toInt
  }
}

trait B extends A {       
  def bar(a: String) = {
    foo(a)
  }
}

class C(a: String) extends A {
  self: B =>

  val b = bar(a)
}

val x = new C("34") with B
dk14
  • 22,206
  • 4
  • 51
  • 88
  • about class abstractness: does it really true? do you have a link to specification which prove that? Even if it's true, I still don't understand why it doesn't work: when we say `new C("34") with B` it's the same as we automatically override `bar` method in abstract C. But we override it with class that extends from A trait, so members of A trait have to be initialized first. – maks Oct 18 '14 at 21:15
  • 1) You may start reading [here](http://stackoverflow.com/questions/11274106/does-a-class-with-a-self-type-of-another-class-make-sense), but it's pretty much obvious - you can't instantiate C without implementation of bar method. And this implementation unknown for C itself because it can be overriden during C's instantiation – dk14 Oct 18 '14 at 23:50
  • 2) It's not the same - trait B overrides only A, the linearization is still B <- A <- C. But initialization is {C -> A -> B} (please refer to [Class Initialization section](http://jim-mcbeath.blogspot.ro/2009/08/scala-class-linearization.html)). So member bar is unknown during C initialization. In other words C and B <- A are initializing independently. – dk14 Oct 19 '14 at 00:27
  • i added more info to my answer; in short: regardless what class C are - its self type doesn't affect initialization order – dk14 Oct 19 '14 at 00:37
  • `bar` is known, but foo is uknown – maks Oct 19 '14 at 21:25
  • both are uncknown actually, but bar is a method, so it has no initialization – dk14 Oct 20 '14 at 01:28
1

The short answer on why you get a NullPointerException is, that initialization of C requires initializing b, which invokes the method stored in val foo, which is not initialized at this point.

Question is, why is foo not initialized at this point? Unfortunately, I cannot fully answer this question, but I'd like to show you some experiments:

If you change the signature of C to extends B, then B, as the superclass of C is instantiated before, leading to no exception being thrown.

In fact

trait A {
  val initA = {
    println("initializing A")
  }
}

trait B extends A {
  val initB = {
    println("initializing B")
  }
}

class C(a: String) {
  self: B => // I imagine this as C has-a B
  val initC = {
    println("initializing C")
  }
}

object Main {
  def main(args: Array[String]): Unit ={
    val x = new C("34") with B
  }
}

prints

initializing C
initializing A
initializing B

while

trait A {
  val initA = {
    println("initializing A")
  }
}

trait B extends A {
  val initB = {
    println("initializing B")
  }
}

class C(a: String) extends B { // C is-a B: The constructor of B is invoked before
  val initC = {
    println("initializing C")
  }
}

object Main {
  def main(args: Array[String]): Unit ={
    val x = new C("34") with B
  }
}

prints

initializing A
initializing B
initializing C

As you can see, the initialization order is different. I imagine the dependency-injection self: B => to be something like a dynamic import (i.e., putting the fields of an instance of B into the scope of C) with a composition of B (i.e., C has-a B). I cannot prove that it is solved like this, but when stepping through with IntelliJ's debugger, the fields of B are not listed under this while still being in the scope.

This should answer the question on why you get a NPE, but leaves the question open on why the mixin is not instantiated first. I cannot think of problems that may occur otherwise (since extending the trait does this basically), so this may very well be either a design choice, or noone thought about this use case. Fortunately, this will only yield problems during instantiation, so the best "solution" is probably to not use mixed-in values during instantiation (i.e., constructors and val/var members).

Edit: Using lazy val is also fine, so you can also define lazy val initC = {initB}, because lazy val are not executed until they are needed. However, if you do not care about side effects or performance, I would prefer def to lazy val, because there is less "magic" behind it.

Kulu Limpa
  • 3,501
  • 1
  • 21
  • 31
0

Declare A.foo to be a lazy val.

Ryan
  • 7,227
  • 5
  • 29
  • 40
  • it's a simplified example. In reality `trait A` is in third party library. But i want to understand the mechanics. Why does it happen? – maks Oct 17 '14 at 17:39