什么是类型标签,我如何使用它?

关于类型标签,我只知道它们以某种方式取代了清单。互联网上的信息很少,不能让我对这个主题有很好的了解。

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

77296 次浏览

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

因此,它们被类型标签所取代,后者使用起来更简单,并且很好地集成到新的反射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

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

TypeTag完全是编译器生成的,这意味着当调用需要这样一个TypeTag的方法时,编译器会创建并填充TypeTag。标签有三种不同的形式:

ClassTag替代了ClassManifest,而TypeTag或多或少替代了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

可以看到,TypeTagtpe方法会得到完整的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
???
}
}

如果用TypeTag替换WeakTypeTag,则抛出错误:

<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中创建类型标签,该类型T具有未解析的类型参数”

Scala的官方文档站点也包含反思指南