Scala:什么是TypeTag,我如何使用它?
我所知道的TypeTags是他们以某种方式取代了Manifests。 互联网上的信息是稀缺的,并没有给我一个很好的主题意识。
所以如果有人在TypeTag上分享了一些有用的材料的链接,包括例子和stream行的用例,我会很高兴。 详细的答案和解释也是受欢迎的。
TypeTag
解决了Scala的types在运行时被擦除(types擦除)的问题。 如果我们想要做
class Foo class Bar extends Foo def meth[A](xs: List[A]) = xs match { case _: List[String] => "list of strings" case _: List[Foo] => "list of foos" }
我们会收到警告:
<console>:23: warning: non-variable type argument String in type pattern List[String]↩ is unchecked since it is eliminated by erasure case _: List[String] => "list of strings" ^ <console>:24: warning: non-variable type argument Foo in type pattern List[Foo]↩ is unchecked since it is eliminated by erasure case _: List[Foo] => "list of foos" ^
为了解决这个问题,Scala引入了Manifests 。 但他们有问题不能代表许多有用的types,如path依赖types:
scala> class Foo{class Bar} defined class Foo scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev warning: there were 2 deprecation warnings; re-run with -deprecation for details m: (f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar])Manifest[f.Bar] scala> val f1 = new Foo;val b1 = new f1.Bar f1: Foo = Foo@681e731c b1: f1.Bar = Foo$Bar@271768ab scala> val f2 = new Foo;val b2 = new f2.Bar f2: Foo = Foo@3e50039c b2: f2.Bar = Foo$Bar@771d16b9 scala> val ev1 = m(f1)(b1) warning: there were 2 deprecation warnings; re-run with -deprecation for details ev1: Manifest[f1.Bar] = Foo@681e731c.type#Foo$Bar scala> val ev2 = m(f2)(b2) warning: there were 2 deprecation warnings; re-run with -deprecation for details ev2: Manifest[f2.Bar] = Foo@3e50039c.type#Foo$Bar scala> ev1 == ev2 // they should be different, thus the result is wrong res28: Boolean = true
因此,它们被TypeTags所取代,它们使用起来更简单,并且很好地集成到新的Reflection API中。 有了它们,我们可以优雅地解决以上有关path依赖types的问题:
scala> def m(f: Foo)(b: f.Bar)(implicit ev: TypeTag[f.Bar]) = ev m: (f: Foo)(b: f.Bar)(implicit ev: reflect.runtime.universe.TypeTag[f.Bar])↩ reflect.runtime.universe.TypeTag[f.Bar] scala> val ev1 = m(f1)(b1) ev1: reflect.runtime.universe.TypeTag[f1.Bar] = TypeTag[f1.Bar] scala> val ev2 = m(f2)(b2) ev2: reflect.runtime.universe.TypeTag[f2.Bar] = TypeTag[f2.Bar] scala> ev1 == ev2 // the result is correct, the type tags are different res30: Boolean = false scala> ev1.tpe =:= ev2.tpe // this result is correct, too res31: Boolean = false
他们也很容易用来检查types参数:
import scala.reflect.runtime.universe._ def meth[A : TypeTag](xs: List[A]) = typeOf[A] match { case t if t =:= typeOf[String] => "list of strings" case t if t <:< typeOf[Foo] => "list of foos" } scala> meth(List("string")) res67: String = list of strings scala> meth(List(new Bar)) res68: String = list of foos
在这一点上,理解使用=:=
(types相等)和<:<
(子types关系)进行相等性检查是非常重要的。 不要使用==
或!=
,除非你完全知道你在做什么:
scala> typeOf[List[java.lang.String]] =:= typeOf[List[Predef.String]] res71: Boolean = true scala> typeOf[List[java.lang.String]] == typeOf[List[Predef.String]] res72: Boolean = false
后者检查结构的平等,这往往不是应该做的,因为它不关心的东西,如前缀(如在例子中)。
TypeTag
是完全由编译器生成的,这意味着编译器在调用期望这样的TypeTag
的方法时创build并填充TypeTag
。 存在三种不同forms的标签:
- scala.reflect.ClassTag
- scala.reflect.api.TypeTags#TypeTag
- scala.reflect.api.TypeTags#WeakTypeTag
ClassTag
代替ClassTag
而TypeTag
或多或less地代替了Manifest
。
前者允许完全使用generics数组:
scala> import scala.reflect._ import scala.reflect._ scala> def createArr[A](seq: A*) = Array[A](seq: _*) <console>:22: error: No ClassTag available for A def createArr[A](seq: A*) = Array[A](seq: _*) ^ scala> def createArr[A : ClassTag](seq: A*) = Array[A](seq: _*) createArr: [A](seq: A*)(implicit evidence$1: scala.reflect.ClassTag[A])Array[A] scala> createArr(1,2,3) res78: Array[Int] = Array(1, 2, 3) scala> createArr("a","b","c") res79: Array[String] = Array(a, b, c)
ClassTag
仅提供在运行时创buildtypes所需的信息(types被擦除):
scala> classTag[Int] res99: scala.reflect.ClassTag[Int] = ClassTag[int] scala> classTag[Int].runtimeClass res100: Class[_] = int scala> classTag[Int].newArray(3) res101: Array[Int] = Array(0, 0, 0) scala> classTag[List[Int]] res104: scala.reflect.ClassTag[List[Int]] =↩ ClassTag[class scala.collection.immutable.List]
如上所述,他们不关心types擦除,因此如果需要“完整”typesTypeTag
:
scala> typeTag[List[Int]] res105: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[scala.List[Int]] scala> typeTag[List[Int]].tpe res107: reflect.runtime.universe.Type = scala.List[Int] scala> typeOf[List[Int]] res108: reflect.runtime.universe.Type = scala.List[Int] scala> res107 =:= res108 res109: Boolean = true
正如人们所看到的, TypeTag
方法tpe
TypeTag
产生一个完整的Type
,这与我们在调用typeOf
时得到的是一样的。 当然,可以同时使用ClassTag
和TypeTag
:
scala> def m[A : ClassTag : TypeTag] = (classTag[A], typeTag[A]) m: [A](implicit evidence$1: scala.reflect.ClassTag[A],↩ implicit evidence$2: reflect.runtime.universe.TypeTag[A])↩ (scala.reflect.ClassTag[A], reflect.runtime.universe.TypeTag[A]) scala> m[List[Int]] res36: (scala.reflect.ClassTag[List[Int]],↩ reflect.runtime.universe.TypeTag[List[Int]]) =↩ (scala.collection.immutable.List,TypeTag[scala.List[Int]])
现在剩下的问题是WeakTypeTag
是什么WeakTypeTag
? 简而言之, TypeTag
表示一个具体types(这意味着它只允许完全实例化的types),而WeakTypeTag
只允许任何types。 大多数情况下,人们不关心哪一个是什么(这意味着应该使用TypeTag
),但是,例如,当使用macros应该使用genericstypes时,它们是需要的:
object Macro { import language.experimental.macros import scala.reflect.macros.Context def anymacro[A](expr: A): String = macro __anymacro[A] def __anymacro[A : c.WeakTypeTag](c: Context)(expr: c.Expr[A]): c.Expr[A] = { // to get a Type for A the c.WeakTypeTag context bound must be added val aType = implicitly[c.WeakTypeTag[A]].tpe ??? } }
如果用WeakTypeTag
代替WeakTypeTag
, TypeTag
抛出一个错误:
<console>:17: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context)(expr: c.Expr[A]): c.Expr[String] found : (c: scala.reflect.macros.Context)(expr: c.Expr[A])(implicit evidence$1: c.TypeTag[A]): c.Expr[A] macro implementations cannot have implicit parameters other than WeakTypeTag evidences def anymacro[A](expr: A): String = macro __anymacro[A] ^
有关TypeTag
和WeakTypeTag
之间差异的更详细的解释,请参阅以下问题: Scalamacros:“无法从具有未parsingtypes参数的typesT创buildTypeTag”
Scala的官方文档站点还包含reflection指南 。