Reputation: 1175
Can someone explain to me (or redirect to resources) why in this particular case the type tag is not "properly" generated:
class A(s: Seq[_]*)
def toto[T: TypeTag](p: Seq[T]): Seq[T] = {
println(typeTag[T].tpe)
p
}
val data = Seq( ("a", "a") )
val x = data.map(_._1)
new A(
toto(x),
toto(data.map(_._2)),
toto[String](data.map(_._2))
)
// output:
// java.lang.String
// Any
// String
As far as I understand, it seems that as my class A
takes "untyped" (well with existential types) sequences, then the compiler does not bother generate the proper type tag when not required explicitly (though it does know the type of data.map(_._2)
it still uses TypeTag[Any]
... ). But it looks quite strange and I wondered if there was a more scientific explanation to this phenomenom.
Also, how can I force the compiler to generate a proper TypeTag[String]
even if I don't want to create special variable (like this x
variable above)?
Upvotes: 3
Views: 110
Reputation: 170713
Nice problem! I have an explanation, but I am not certain it's right (80%, let's say).
As very often with Scala type inference question, you need to be aware of expected types. In this case, all arguments of new A
are typed with expected type Seq[_]
, which is the same as Seq[Any]
because of covariance. So:
toto(data.map(_._2))
is typed with expected type Seq[Any]
; data.map(_._2)
is typed with expected type Seq[Any]
. The signature of Seq#map
is
def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Seq[A], B, That]): That
so That
is inferred based on the expected type and a suitable implicit bf
is found. I am not actually sure if B
is inferred to String
or Any
, but it probably doesn't matter.
In the val x = data.map(_._1)
, there is no expected type so B
is inferred to String
, an implicit bf
is found based on A
and B
and then That
is inferred from the complete type of bf
.
toto(x)
is typed with expected type Seq[Any]
; x
is typed with expected type Seq[Any]
, but it already has type Seq[String]
and the expected type doesn't matter.
Upvotes: 2
Reputation: 1770
I would like to extend answer of @AlexeyRomanov by possible solution how to force compiler to evaluate specific type:
From here I took idea for forcing type difference:
sealed class =!=[A,B]
trait LowerPriorityImplicits {
implicit def equal[A]: =!=[A, A] = sys.error("should not be called")
}
object =!= extends LowerPriorityImplicits {
implicit def nequal[A,B](implicit same: A =:= B = null): =!=[A,B] =
if (same != null) sys.error("should not be called explicitly with same type")
else new =!=[A,B]
}
Now we can add limitation for parameter to toto
:
class A(s: Seq[_]*)
def toto[T: TypeTag](p: Seq[T])(implicit guard: T =!= Any): Seq[T] = {
println(typeTag[T].tpe)
p
}
val data = Seq(("a", "a"))
val x = data.map(_._1)
new A(
toto(x),
toto(data.map(_._2)),
toto[String](data.map(_._2))
)
And output I have
java.lang.String
java.lang.String
String
Upvotes: 1