Scala:什么是TypeTag以及如何使用它?

Ser*_*iss 355 types scala reification scala-2.10

我所知道的TypeTags就是他们以某种方式取代了Manifest.互联网上的信息很少,并没有让我对这个主题有很好的认识.

所以,如果有人在TypeTag上分享了一些有用的资料,包括例子和流行的用例,我会很高兴.我们也欢迎详细的解答和解释.

kir*_*uku 551

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"
}
Run Code Online (Sandbox Code Playgroud)

我们会收到警告:

<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"
                 ^
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,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
Run Code Online (Sandbox Code Playgroud)

因此,它们被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
Run Code Online (Sandbox Code Playgroud)

它们也很容易用来检查类型参数:

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
Run Code Online (Sandbox Code Playgroud)

在这一点上,理解使用=:=(类型相等)和<:<(子类型关系)进行相等性检查是非常重要的.永远不要使用==!=,除非你完全知道你做了什么:

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
Run Code Online (Sandbox Code Playgroud)

后者检查结构相等性,这通常不是应该做的,因为它不关心诸如前缀之类的事情(如示例中所示).

A TypeTag是完全由编译器生成的,这意味着编译器在TypeTag调用期望这样的方法时创建并填充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)
Run Code Online (Sandbox Code Playgroud)

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]
Run Code Online (Sandbox Code Playgroud)

如上所述,他们不关心类型擦除,因此如果想要"完整"类型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
Run Code Online (Sandbox Code Playgroud)

正如人们所看到的,方法tpeTypeTag结果在一个完整的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]])
Run Code Online (Sandbox Code Playgroud)

现在剩下的问题是什么意思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
    ???
  }
}
Run Code Online (Sandbox Code Playgroud)

如果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]
                                                      ^
Run Code Online (Sandbox Code Playgroud)

有关这些问题之间差异的更详细解释TypeTag,WeakTypeTag请参阅此问题:Scala Macros:"无法从具有未解析类型参数的类型T创建TypeTag"

Scala的官方文档站点还包含Reflection指南.

  • 感谢您的回答!一些评论:1)`==`for types表示结构相等,而不是引用相等.`=:=`考虑类型等价(即使是非显而易见的类型,例如来自不同镜像的前缀的等价),2)`TypeTag`和`AbsTypeTag`都基于镜像.不同之处在于`TypeTag`只允许完全实例化的类型(即没有任何类型参数或引用抽象类型成员),3)详细解释如下:http://stackoverflow.com/questions/12093752 (19认同)
  • 我真的希望Scala编译器能够在某些时候摆脱不推荐使用的功能,以使可用功能集更加正交.这就是我喜欢新的宏支持的原因,因为它提供了清理语言的潜力,将独立库中不属于基本语言的一些功能分开. (11认同)
  • 4)清单存在无法表示许多有用类型的问题.本质上,它们只能表达类型引用(普通类型,如`Int`和泛型类型,如`List [Int]`),而忽略了类似Scala类型,例如细化,路径依赖类型,存在性,注释类型.同时显示是一个螺栓,因此他们不能使用编译器提供的大量知识,比如计算一个类型的线性化,找出一个类型是否是另一个类型,等等. (10认同)
  • 5)对比度类型标签不是"更好地集成",它们只是与新的反射API集成(不像没有与任何东西集成的清单).这提供了类型标签访问编译器的某些方面,例如`Types.scala`(知道如何支持类型的7kloc代码一起工作),`Symbols.scala`(知道符号表如何工作的3kloc代码)等 (9认同)
  • 6)`ClassTag`是`ClassManifest`的精确替代品,而`TypeTag`或多或少是'Manifest`的替代品.或多或少,因为:1)类型标签不带删除,2)清单是一个很大的黑客,我们放弃使用类型标签模拟其行为.当你需要删除和删除类型时,可以使用ClassTag和TypeTag上下文绑定来修复#1,并且通常不关心#2,因为它可以丢弃所有hack并使用完整的反射API代替. (9认同)
  • @EugeneBurmako:感谢有用的评论.我添加了一些提供的信息,但并不关心信息内部是如何完成的.如果你认为它们非常重要,可以提出建议,如果你能写出另一个答案那就太好了. (3认同)
  • 看起来很棒!还有一个建议.请调整有关AbsTypeTag和TypeTag之间差异的信息."简而言之,TypeTag代表一种具体类型......而AbsTypeTag代表一种抽象类型"."抽象的"听起来含糊不清.我会说"允许任何类型". (3认同)
  • 同样在你的宏示例中"如果想要使用A,则需要使用AbsTypeTag来获取正确的类型".我只是说"为A添加一个Type来添加c.AbsTypeTag上下文绑定并隐式使用[c.AbsTypeTag [A]].tpe".这是因为严格来说,可以直接从c.macroApplication获取A的类型,但这样不太方便. (3认同)