Trait runtime type of type parameter through TypeTag when used with Existential type in Scala

十年热恋 提交于 2020-03-20 11:57:19

问题


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?


回答1:


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
}


来源:https://stackoverflow.com/questions/29380839/trait-runtime-type-of-type-parameter-through-typetag-when-used-with-existential

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!