1
votes

Consider the following case:

trait A {
    protected val mydata = ???

    def f(args) = ??? //uses mydata
}

class B
class C

class D(arg1: String) extends B with A {
    override val mydata = ??? /// some calculation based on arg1
}

class E(arg1: String) extends C with A{
    override val mydata = ??? /// some calculation based on arg1
}

A must be a trait as it is used by different unrelated classes. The problem is how to implement the definition of mydata.

The standard way (suggested in many places would be to define mydata as def and override it in the children. However, if f assumes mydata never changes then it can cause issues when some child extends with a function which changes between calls instead of with a val.

Another way would be to do:

trait A {
  protected val mydata = g
  protected def g()
}

The problem with this (beyond adding another function) is that if g depends on construction variables in the child then these must become members of the child (which can be a problem for example if the data is large and given in the construction):

 class D(arg1: Seq[String]) {
     def g() = ??? // some operation on arg1
 }

If I leave the val in the trait as abstract I can reach issues such as those found here).

What I am looking for is a way to define the value of the val in the children, ensuring it would be a val and without having to save data for late calculations. Something similar as to how in java I can define a final val and fill it in the constructor

1

1 Answers

1
votes

The standard way (suggested in many places would be to define mydata as def and override it in the children... If I leave the val in the trait as abstract I can reach issues such as those found here).

This is a common misunderstanding, shown in the accepted answer to the linked question as well. The issue is implementing as a val, which you require anyway. Having a concrete val which is overridden only makes it worse: abstract one can at least be implemented by a lazy val. The only way to avoid the issue for you is to ensure mydata is not accessed in a constructor of A or its subtypes, directly or indirectly, until it's initialized. Using it in f is safe (provided f is not called in a constructor, again, which would be an indirect access to mydata).

If you can ensure this requirement, then

trait A {
    protected val mydata
    def f(args) = ??? //uses mydata
}

class D(arg1: String) extends B with A {
    override val mydata = ??? /// some calculation based on arg1
}

class E(arg1: String) extends C with A{
    override val mydata = ??? /// some calculation based on arg1
}

is exactly the correct definition.

If you can't, then you have to live with your last solution despite the drawback, but mydata needs to be lazy to avoid similar initialization order issues, which would already give the same drawback on its own.