Search code examples
scalatypeclassabstract-type

Why a concrete implementation of an abstract type cannot be used to infer ClassTag?


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?


Solution

  • 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.