Scala:什么是TypeTag?如何使用?


361

我对TypeTag的了解仅是它们以某种方式取代了Manifests。互联网上的信息稀缺,无法使我对这个主题有很好的了解。

因此,如果有人共享指向TypeTag上一些有用材料的链接(包括示例和流行的用例),我将感到非常高兴。也欢迎提供详细的答案和解释。


1
从斯卡拉文档下面的文章介绍了双方什么和为什么类型的变量,以及如何在你的代码中使用它们的:docs.scala-lang.org/overviews/reflection/...
btiernay

Answers:


563

A TypeTag解决了在运行时擦除Scala类型的问题(类型擦除)。如果我们想做

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。但是他们有一个问题,就是无法表示很多有用的类型,例如路径依赖类型:

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

因此,它们被TypeTag取代,它们不仅使用起来更加简单,而且可以很好地集成到新的Reflection API中。有了它们,我们可以优雅地解决上述有关路径依赖类型的问题:

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

它们还易于使用来检查类型参数:

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

在这一点上,了解使用=:=(类型相等)和<:<(子类型关系)进行相等性检查非常重要。请勿使用==!=,除非您完全知道自己的操作:

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

后者检查结构是否相等,通常不应该执行此操作,因为它不关心前缀之类的东西(如示例中所示)。

A TypeTag是完全由编译器生成的,这意味着编译器会在TypeTag调用需要此类的方法时创建并填写a TypeTag。标签存在三种不同形式:

ClassTag替代品,ClassManifestTypeTag或多或少是替代品Manifest

前者可以完全使用通用数组:

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 仅提供在运行时创建类型所需的信息(已擦除类型):

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]

从上面可以看到,他们并不关心类型擦除,因此如果要使用“完整”类型,TypeTag则应使用:

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

可以看到,结果方法tpeTypeTagfull Type,与typeOf调用时所得到的结果相同。当然,可以同时使用ClassTagTypeTag

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?简而言之,TypeTag代表具体类型(这意味着它仅允许完全实例化的类型),而WeakTypeTag仅允许任何类型。大多数时候,人们并不关心哪个是什么(TypeTag应该使用什么意思),但是例如,当使用宏时应该使用泛型类型,则需要它们:

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
    ???
  }
}

如果其替换WeakTypeTagTypeTag引发错误:

<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]
                                                      ^

有关两者之间差异的更详细说明TypeTagWeakTypeTag请参见以下问题:Scala宏:“无法从具有未解析类型参数的类型T创建TypeTag”

Scala的官方文档站点还包含反射指南


19
感谢您的回答!一些评论:1)==对于类型表示结构相等,而不是引用相等。=:=考虑到型等价(即使非显而易见的,如前缀的等价来自不同的反射镜),2)两个TypeTagAbsTypeTag基于反射镜。不同之处在于TypeTag仅允许完全实例化的类型(即,没有任何类型参数或引用抽象类型成员),3)此处有详细说明:stackoverflow.com/questions/12093752
Eugene Burmako 2012年

10
4)清单存在无法代表许多有用类型的问题。本质上,它们只能表达类型引用(普通类型,例如Int,普通类型,例如List[Int]),而忽略诸如改进,路径依赖类型,存在,注释类型之类的Scala类型。也体现上螺栓,使他们不能使用庞大的知识编译素显示,比方说,计算一个类型的线性化,找出一个类型是否亚型另一个等等
尤金Burmako

9
5)对比类型标记不是“更好地集成”的,它们只是与新的反射API集成在一起(与没有与任何内容集成的清单不同)。这提供了类型的变量访问,如编译器的某些方面Types.scala(那知道类型如何支持协同工作代码7kloc), Symbols.scala(代码3kloc知道如何符号表的工作),等等
尤金Burmako

9
6)ClassTag是的精确替代ClassManifest,而TypeTag或多或少是的替代Manifest。或多或少是因为:1)类型标签不带有擦除,2)清单是个大问题,我们放弃了使用类型标签来模拟其行为。当您同时需要擦除和类型时,可以同时使用ClassTag和TypeTag上下文边界来固定#1,而且通常不关心#2,因为可以抛弃所有技巧并使用成熟的反射API代替。
Eugene Burmako 2012年

11
我真的希望Scala编译器可以在某个时候摆脱过时的功能,以使可用功能集更加正交。这就是为什么我喜欢新的宏支持的原因,因为它提供了清理语言的潜力,将独立库中的某些功能(不是基本语言的一部分)分开了。
Alexandru Nedelcu 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.