简体   繁体   English

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

[英]Scala: What is a TypeTag and how do I use it?

All I know about TypeTags is that they somehow replaced Manifests. 我所知道的TypeTags就是他们以某种方式取代了Manifest。 Information on the Internet is scarce and doesn't provide me with a good sense of the subject. 互联网上的信息很少,并没有让我对这个主题有很好的认识。

So I'd be happy if someone shared a link to some useful materials on TypeTags including examples and popular use-cases. 所以,如果有人在TypeTag上分享了一些有用的资料,包括例子和流行的用例,我会很高兴。 Detailed answers and explanations are also welcome. 我们也欢迎详细的解答和解释。

A TypeTag solves the problem that Scala's types are erased at runtime (type erasure). TypeTag解决了Scala的类型在运行时被擦除的问题(类型擦除)。 If we wanna do 如果我们想做

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

we will get warnings: 我们会收到警告:

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

To solve this problem Manifests were introduced to Scala. 为了解决这个问题,Scala引入了清单 But they have the problem not being able to represent a lot of useful types, like path-dependent-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

Thus, they are replaced by TypeTags , which are both much simpler to use and well integrated into the new Reflection API. 因此,它们被TypeTag取代,它们更易于使用并且很好地集成到新的Reflection API中。 With them we can solve the problem above about path-dependent-types elegantly: 有了它们,我们可以优雅地解决上面关于路径依赖类型的问题:

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

They are also easy to use to check type parameters: 它们也很容易用来检查类型参数:

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

At this point, it is extremely important to understand to use =:= (type equality) and <:< (subtype relation) for equality checks. 此时,理解使用=:= (类型相等)和<:< (子类型关系)进行相等性检查是非常重要的。 Do never use == or != , unless you absolutely know what you do: 永远不要使用==!= ,除非你完全知道你做了什么:

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

The latter checks for structural equality, which often is not what should be done because it doesn't care about things such as prefixes (like in the example). 后者检查结构相等性,这通常不是应该做的,因为它不关心诸如前缀之类的事情(如示例中所示)。

A TypeTag is completely compiler-generated, that means that the compiler creates and fills in a TypeTag when one calls a method expecting such a TypeTag . TypeTag完全编译器生成的,这意味着,编译器创建并填充在TypeTag当一个呼叫期待这样一种方法TypeTag There exist three different forms of tags: 存在三种不同形式的标签:

ClassTag substitutes ClassManifest whereas TypeTag is more or less the replacement for Manifest . ClassTag替代ClassManifest TypeTagTypeTag或多或少取代了Manifest

The former allows to fully work with generic arrays: 前者允许完全使用通用数组:

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 provides only the information needed to create types at runtime (which are type erased): 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]

As one can see above, they don't care about type erasure, therefore if one wants "full" types TypeTag should be used: 如上所述,他们不关心类型擦除,因此如果想要“完整”类型,应使用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

As one can see, method tpe of TypeTag results in a full Type , which is the same we get when typeOf is called. 可以看出, TypeTag方法tpe产生一个完整的Type ,这与我们在调用typeOf时得到的相同。 Of course, it is possible to use both, ClassTag and TypeTag : 当然,可以同时使用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]])

The remaining question now is what is the sense of WeakTypeTag ? 现在剩下的问题是WeakTypeTag的意义是什么? In short, TypeTag represents a concrete type (this means it only allows fully instantiated types) whereas WeakTypeTag just allows any type. 简而言之, TypeTag表示具体类型(这意味着它只允许完全实例化的类型),而WeakTypeTag只允许任何类型。 Most of the time one does not care which is what (which means TypeTag should be used), but for example, when macros are used which should work with generic types they are needed: 大多数情况下,人们并不关心哪个是什么(这意味着应该使用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
    ???
  }
}

If one replaces WeakTypeTag with TypeTag an error is thrown: 如果一个取代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]
                                                      ^

For a more detailed explanation about the differences between TypeTag and WeakTypeTag see this question: Scala Macros: “cannot create TypeTag from a type T having unresolved type parameters” 有关TypeTagWeakTypeTag之间差异的更详细说明,请参阅此问题: Scala宏:“无法从具有未解析类型参数的类型T创建TypeTag”

The official documentation site of Scala also contains a guide for Reflection . Scala的官方文档站点还包含Reflection指南

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM