I have trait
with type parameter. To get the runtime type I use TypeTag
. However, when this trait
(and its classes) are used with existential type
in a Collection, e.g. List
or Map
, TypeTag
is "lost".
Here is an example of standard way to use Type Tag:
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> trait Animal[T] {
| def typeT()(implicit t: TypeTag[T]) = t.tpe
| }
defined trait Animal
scala>
scala> class Dog extends Animal[Int]
defined class Dog
scala> class Cat extends Animal[String]
defined class Cat
scala>
scala> val dog = new Dog
dog: Dog = Dog@4aa88c93
scala> val cat = new Cat
cat: Cat = Cat@2281e252
scala> dog.typeT
res46: reflect.runtime.universe.Type = Int
scala> cat.typeT
res47: reflect.runtime.universe.Type = String
As you can see, so far so good, the method typeT
defined and implemented in the trait Animal
works. However, when used with List
and existential types, it failed working:
scala> val aa: List[Animal[_]] = List(dog, cat, dog)
aa: List[Animal[_]] = List(Dog@4aa88c93, Cat@2281e252, Dog@4aa88c93)
scala> aa(0).typeT
res52: reflect.runtime.universe.Type = _$1
scala> aa(1).typeT
res53: reflect.runtime.universe.Type = _$1
Explicit casting (like the following) for sure worked. But most of the time what is given is List[Anima[_]]
. If another level of TypeCast is necessary, and how?
scala> aa(0)
res55: Animal[_] = Dog@4aa88c93
scala> aa(0).asInstanceOf[Dog]
res56: Dog = Dog@4aa88c93
scala> aa(0).asInstanceOf[Dog].typeT
res57: reflect.runtime.universe.Type = Int
I understand that aa(0)
is an Animal[_]
which is the reason. But still, aa(0)
is not only an Animal[_]
, but a Dog
. Why the typeT
(or TypeTag
) could not be used as if it were a normal method?
The problem here is that typeT()
is a method, so it will return a different value depending on your knowledge of the Animal
. If the compiler can prove you have an Animal[Int]
, then it can easily get a TypeTag[Int]
. But with an existential type in List[Animal[_]]
, you lose the type information contained in Animal
. So when you select an arbitrary element from the list, all you know is that it's an Animal[_]
when typeT
is called, and nothing else. typeT
does not know about the type parameter T
for each instance. It has no way of proving it in this context.
The type cast of course works, because asInstanceof[Animal[Cat]]
tells the compiler to forget what it knows. This if course can throw a ClassCastException
when you get it wrong.
One way you can get it to work is by requiring the implicit TypeTag[T]
on instantiation of an Animal[T]
, and storing that value, rather than resolving it within a method. Unfortunately, this means you cannot use a trait.
abstract class Animal[T](implicit tt: TypeTag[T]) {
val tpe = tt.tpe
}
class Dog extends Animal[Int]
class Cat extends Animal[String]
val dog = new Dog
val cat = new Cat
scala> val aa: List[Animal[_]] = List(cat, dog, cat)
aa: List[Animal[_]] = List(Cat@5a9faacf, Dog@675c379d, Cat@5a9faacf)
scala> aa(0).tpe
res6: reflect.runtime.universe.Type = String
scala> aa(1).tpe
res7: reflect.runtime.universe.Type = Int
Alternatively, you could express it using a little syntactic sugar on the implicit parameters:
abstract class Animal[T: TypeTag] {
val tpe = implicitly[TypeTag[T]].tpe
}