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

Posted

技术标签:

【中文标题】什么是 TypeTag 以及如何使用它?【英文标题】:What is a TypeTag and how do I use it? 【发布时间】:2012-08-31 15:43:27 【问题描述】:

关于 TypeTag,我所知道的只是它们以某种方式取代了 Manifest。 Internet 上的信息很少,无法让我很好地了解该主题。

因此,如果有人分享指向 TypeTags 上一些有用材料的链接,包括示例和流行用例,我会很高兴。也欢迎详细解答和解释。

【问题讨论】:

Scala 文档中的以下文章描述了类型标签的内容和原因,以及如何在代码中使用它们:docs.scala-lang.org/overviews/reflection/… 【参考方案1】:

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

为了解决这个问题Manifests 被引入了 Scala。但是他们的问题是不能表示很多有用的类型,比如路径依赖类型:

scala> class Fooclass 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 取代,它们使用起来更简单,并且很好地集成到了新的反射 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

此时,了解使用=:=(类型相等)和&lt;:&lt;(子类型关系)进行相等检查非常重要。切勿使用==!=,除非您完全知道自己在做什么:

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 的方法时,编译器会创建并填充 TypeTag。存在三种不同形式的标签:

scala.reflect.ClassTag scala.reflect.api.TypeTags#TypeTag scala.reflect.api.TypeTags#WeakTypeTag

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

正如我们所见,TypeTag 的方法 tpe 会产生一个完整的 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
    ???
  

如果将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]
                                                      ^

有关TypeTagWeakTypeTag 之间差异的更详细说明,请参阅此问题:Scala Macros: “cannot create TypeTag from a type T having unresolved type parameters”

Scala 的官方文档站点也包含一个guide for Reflection。

【讨论】:

感谢您的回答!一些 cmets: 1) == for types 表示结构相等,而不是引用相等。 =:= 考虑到类型等价(即使是不明显的,例如来自不同镜像的前缀等价),2)TypeTagAbsTypeTag 都基于镜像。不同的是TypeTag只允许完全实例化类型(即没有任何类型参数或引用抽象类型成员),3)详细解释在这里:***.com/questions/12093752 4) 清单存在无法表示很多有用类型的问题。本质上它们只能表示类型引用(诸如Int 之类的普通类型和List[Int] 之类的泛型类型),而忽略了诸如此类的Scala 类型。细化,路径依赖类型,存在,注释类型。清单也很重要,因此它们无法使用编译器拥有的大量知识来计算类型的线性化,找出一种类型是否是另一种类型的子类型等。 5) 对比类型标签没有“更好地集成”,它们只是与新的反射 API 集成(不像清单没有与任何东西集成)。这提供了对编译器某些方面的类型标签访问,例如Types.scala(7kloc 代码知道如何支持类型一起工作),Symbols.scala(3kloc 代码知道符号表如何工作)等 6) ClassTagClassManifest 的完全替代品,而TypeTag 或多或少是Manifest 的替代品。或多或少,因为:1)类型标签不携带擦除,2)清单是一个大黑客,我们放弃了用类型标签模拟它的行为。当您需要擦除和类型时,可以通过使用 ClassTag 和 TypeTag 上下文边界来修复#1,并且通常不关心#2,因为可以丢弃所有黑客并使用成熟的反射 API而是。 我真的希望 Scala 编译器能够在某个时候摆脱不推荐使用的特性,以使可用特性集更加正交。这就是我喜欢新宏支持的原因,因为它提供了清理语言的潜力,将一些不属于基本语言的独立库中的功能分开。

以上是关于什么是 TypeTag 以及如何使用它?的主要内容,如果未能解决你的问题,请参考以下文章

关于jvm范型和scala implicit隐式参数以及classTag[T] typeTag[T]的一点思考

关于jvm范型和scala implicit隐式参数以及classTag[T] typeTag[T]的一点思考

什么是 .pem 文件以及如何使用它?

什么是 Redis pubsub 以及如何使用它?

什么是 Future 以及如何使用它?

什么是 memoization 以及如何在 Python 中使用它?