scalatypeclasscovarianceexistential-typesingleton-type

How to return wildcard generic?


I have a type alias with parameter and I would like to return the instance of different parameter types from a method:

type TC[T] = (ClassTag[T], Option[T])

def gen(x: Int): TC[_] = x match {
  case 0 => (classTag[Int], Option[Int](0))
  case _ => (classTag[String], Option[String](""))
}

This does not work and gives me error:

error: type mismatch; found : (scala.reflect.ClassTag[_ >: Int with String], Option[Any]) required: TC[] (which expands to) (scala.reflect.ClassTag[$1], Option[_$1]) forSome { type _$1 }

And I tried to use Any instead of wildcard _, and it still does not work:

def gen(x: Int): TC[Any]

On line 2: error: type mismatch; found : scala.reflect.ClassTag[Int] required: scala.reflect.ClassTag[Any] Note: Int <: Any, but trait ClassTag is invariant in type T. You may wish to investigate a wildcard type such as _ <: Any. (SLS 3.2.10) case _ => (classTag[String], Some("")) ^ On line 3: error: type mismatch; found : scala.reflect.ClassTag[String] required: scala.reflect.ClassTag[Any] Note: String <: Any, but trait ClassTag is invariant in type T. You may wish to investigate a wildcard type such as _ <: Any. (SLS 3.2.10)

How can this be achieved?


Solution

  • It's better to return specific type rather than existential. If you want gen to return different types depending on its argument then actually gen is a polymorphic function. Try the following approach with type class and singleton types.

    type TC[T] = (ClassTag[T], Option[T])
    
    trait Gen[X <: Int] {
      type Out
      def apply(x: X): Out
    }
    trait LowPriorityGen {
      type Aux[X <: Int, Out0] = Gen[X] { type Out = Out0 }
      def instance[X <: Int, Out0](f: X => Out0): Aux[X, Out0] = new Gen[X] {
        override type Out = Out0
        override def apply(x: X): Out0 = f(x)
      }
    
      implicit def default[X <: Int : ValueOf]: Aux[X, TC[String]] = instance(_ => (classTag[String], Option[String]("")))
    }
    object Gen extends LowPriorityGen {
      implicit val zero: Aux[0, TC[Int]] = instance(_ => (classTag[Int], Option[Int](0)))
    }
    
    def gen[X <: Int with Singleton](x: X)(implicit g: Gen[X]): g.Out = g(x)
    
    gen(0) //(Int,Some(0))
    gen(1) //(java.lang.String,Some())
    

    Reasons are similar to those in the previous question. ClassTag and Option have different variance.

    Try

    type TC[T] = (ClassTag[_ <: T], Option[T])
    
    def gen(x: Int): TC[_] = x match {
      case 0 => (classTag[Int], Option[Int](0))
      case _ => (classTag[String], Option[String](""))
    }
    

    Even if you can't encode desirable property in types you still can check it at compile time with check in right hand side of pattern matching.

    def gen(x: Int): (ClassTag[_], Option[_]) = x match {
      case 0 => check(classTag[Int], Option[Int](0))
      case _ => check(classTag[String], Option[String](""))
    }
    
    def check[T](classTag: ClassTag[T], option: Option[T]): (ClassTag[T], Option[T]) = (classTag, option)