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?
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)
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.