How to return wildcard generic?

2019-09-08 05:37发布

问题:

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?

回答1:

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)


回答2:

It turned out because Tuple4 type parameters are covariant: Tuple4[+T1, +T2, +T3, +T4], it does not work well with invariant type classes like ClassTag.

I create a wrapper class that takes invariant type parameters:

case class TC[T](ct: ClassTag[T], o: Option[T])

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

Voila it works.