Considering the following code:
object DelayedClassTagInference {
trait SS {
type TT <: Any
implicit val ctg: ClassTag[TT] = implicitly[ClassTag[TT]]
val fakeCtg: ClassTag[None.type] = implicitly[ClassTag[None.type]]
}
class Sub1 extends SS {
override final type TT = Int
}
class Sub2 extends SS {
override final type TT = Double
}
class Sub3 extends SS {
override final type TT = String
}
}
class DelayedClassTagInference extends FunSpec {
import DelayedClassTagInference._
it("") {
val sub1 = new Sub1()
println(sub1.fakeCtg)
println(sub1.ctg)
}
}
When Sub1 & Sub2 are initialised, type TT is already determined, so ClassTag[Int] and ClassTag[Double] can be easily inferred by using type class rules.
Unfortunately, when I run the above code. I got the following result:
scala.None$
null
So the value of ctg is null, besides from triggering an NullPointerException, this also doesn't make sense. Is it a scala bag that should be fixed later?
Remove modifier implicit
for val ctg
and you'll see that your code doesn't compile. You shouldn't define implicit ClassTag
/TypeTag
/WeakTypeTag
manually, they should be generated by compiler automatically when type is known.
Actually when you call implicitly[ClassTag[TT]]
the implicit val ctg: ClassTag[TT]
you're defining right now is used, that's why it's null
at runtime.
Implicits are resolved at compile time and, when you call sub1.ctg
, resolving which .ctg
is called happens at runtime (that's how subtyping polymorphism works). At compile time it's not known yet that it's Sub1#ctg
.
Replace
implicit val ctg: ClassTag[TT] = implicitly[ClassTag[TT]]
with
def ctg(implicit tag: ClassTag[TT]): ClassTag[TT] = implicitly[ClassTag[TT]]
and you'll have Int
at runtime instead of null
.