Here is a bit of code I've distilled down as much as I can:
trait CakeLayer[A] extends {
// typeclass hack to make it play nice with traits
implicit def requireTypeclass: MyTypeclass[A]
val typeclassInVal = requireTypeclass
/* other stuff */
}
class FooImpl
object FooImpl {
implicit object FooImplIsTypeclass extends MyTypeclass[FooImpl]
}
// This works perfectly
class Foo extends CakeLayer[FooImpl] {
override def requireTypeclass = implicitly
}
// This gives me NullPointerException
// Occurs in a function of my "Bar" that contains an anonymous function
// which uses typeclassInVal. it is the first access to that val
// probably due to the parameter in the typeclass constructor?
class BarImpl(x: Int)
object BarImpl {
class BarImplIsTypeclass(x: Int) extends MyTypeclass[BarImpl]
}
class Bar(x: Int) extends CakeLayer[BarImpl] {
val typeclass = new BarImpl.BarImplIsTypeclass(x)
override def requireTypeclass = typeclass
}
Simply variable initialization order, which start with ancestors.
First, typeclassInVal
in ancestor trait is initialized. To do that, requireTypeclass
is called. It is overriden in Bar
and access val typeclass
, not yet initialized, so null at this point. So typeclassInVal
is initialized to null once and for all, and you get an NPE the first time it is used.
Simple workaround could be not having a val, but just a def in the ancestor trait, or having a lazy val.