问题
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.
来源:https://stackoverflow.com/questions/57733018/how-to-return-wildcard-generic