Search code examples
scalagenericstypeclasscovarianceexistential-type

How to pass extential type of tuple to invariant generic function?


I have an extential type of tuple, and I want to pass it to a generic function (I use ClassTag in this example but it is a custom type class with invariant type parameter):

type TC = (ClassTag[T], Option[T]) forSome {type T}
def foo[T](ct: ClassTag[T], o: Option[T]) = {}

val tc: TC = (classTag[String], Option[String](null))
foo(tc._1, tc._2)

This gives me error:

error: type mismatch; found : scala.reflect.ClassTag[T] required: scala.reflect.ClassTag[Any] Note: T <: Any, but trait ClassTag is invariant in type T. You may wish to investigate a wildcard type such as _ <: Any. (SLS 3.2.10) foo(tc._1, tc._2)

I wanna ensure the type of two parameters ct and o uses the same parameter type T and I thought extential type should ensure this, but it does not seem to work.

However if I don't use tuple and only use ClassTag, it works fine:

type TC = ClassTag[T] forSome {type T}
def foo[T](ct: ClassTag[T]) = {}

val tc: TC = classTag[String]
foo(tc)

So the previous error of ClassTag invariance does not make sense.

Why does it not work when I use a tuple? How can I make it work?


Solution

  • For val tc: TC = classTag[String], Option[String](null)) tc._1 has type ClassTag[_] (which is different from any ClassTag[T] e.g. ClassTag[Any] because ClassTag is invariant), tc._2 has type Option[_], which is the same as Option[Any] because Option is covariant

    implicitly[Option[_] =:= Option[Any]]
    implicitly[Option[Any] =:= Option[_]]
    

    An existential type 𝑇 forSome { 𝑄 } where 𝑄 contains a clause type 𝑡[tps]>:𝐿<:𝑈 is equivalent to the type 𝑇′ forSome { 𝑄 } where 𝑇′ results from 𝑇 by replacing every covariant occurrence of 𝑡 in 𝑇 by 𝑈 and by replacing every contravariant occurrence of 𝑡 in 𝑇 by 𝐿.

    https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#existential-types

    So in

    def foo[T](ct: ClassTag[T], o: Option[T]) = {}
    foo(tc._1, tc._2)
    

    we have the type mismatch. Any and _ (aka arbitrary T) are different types.

    Try to use universal quantification rather than existential

    type TC[T] = (ClassTag[T], Option[T])
    def foo[T](ct: ClassTag[T], o: Option[T]) = {}
    
    val tc: TC[String] = (classTag[String], Option[String](null))
    foo(tc._1, tc._2)