4

The code below throws a java.lang.NullPointerException because the trait is initialized prematurely.

trait DummyTrait {
  def intSeq: Seq[Int]
  require(intSeq.exists(_ > 2))
}
object Dummy extends DummyTrait {
  val extraIntSeq: Seq[Int] = Seq(-2,-3)
  override def intSeq = Seq(1,0,4) ++ extraIntSeq
}

Dummy.intSeq

However, the simple change below fixes the issue, but I couldn't understand why.

trait DummyTrait {
  def intSeq: Seq[Int]
  require(intSeq.exists(_ > 2))
}
object Dummy extends DummyTrait {
  lazy val extraIntSeq: Seq[Int] = Seq(-2,-3) // using `def` also works
  override def intSeq = Seq(1,0,4) ++ extraIntSeq
}

Dummy.intSeq

I found this documentation about overriden val being NULL, but it doesn't seem to be applicable to the example above since the fix doesn't involve a variable that is defined in the interface of the superclass.

Also, is the solution presented above an antipattern? As someone who is developing the trait, how should I enforce requirements to abstract values without surprising the user that implements the child class with a potential NullPointerException?

Obs.: I am using Scala version 2.13.0

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66

2 Answers2

4

In the first case the extraIntSeq is initialised in the constructor after super constructor is called hence the NPE

  object Dummy extends DummyTrait {
    private[this] val extraIntSeq: Seq = _;
    ...
    def <init>($outer: O): Dummy.type = {
      Dummy.super.<init>();
      Dummy.this.extraIntSeq = ... // note that it comes AFTER super
      ()
    }
  }

whilst in the latter lazy val is turned into method.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
  • cool, but do you have suggestions on resources to learn more about how this compilation process works? It still seems very obscure for me. – Pedro Igor A. Oliveira May 23 '20 at 02:00
  • @PedroIgorA.Oliveira https://docs.scala-lang.org/overviews/compiler-options/index.html https://tpolecat.github.io/2017/04/25/scalac-flags.html For example `scalacOptions += "-Xprint:parse"` or `scalacOptions += "-Xprint:typer"`. – Dmytro Mitin May 26 '20 at 00:33
3

So far you can use early initializer

object Dummy extends {
  val extraIntSeq: Seq[Int] = Seq(-2,-3)
  override val intSeq = Seq(1,0,4) ++ extraIntSeq
} with DummyTrait

In Scala, what is an "early initializer"?

https://dotty.epfl.ch/docs/reference/dropped-features/early-initializers.html

https://contributors.scala-lang.org/t/proposal-to-remove-early-initializers-from-the-language/2144

https://github.com/scala/scala-dev/issues/513

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • hmmm, interesting, but I think it's similar to the `lazy val` solution in the sense that it still leaves the responsibility for who is implementing the subclass. – Pedro Igor A. Oliveira May 23 '20 at 01:54
  • 1
    @PedroIgorA.Oliveira Well, in Dotty trait parameters are instead of early initializers https://scastie.scala-lang.org/q8wSND9CRkWTqcs0xTLgFA So who is implementing the subclass has to provide constructor parameter. – Dmytro Mitin May 23 '20 at 02:16
  • Nice, much better! I guess I'll have to wait for scala 3.0 then. – Pedro Igor A. Oliveira May 23 '20 at 02:25