首页 文章

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

提问于
浏览
341

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

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

1 回答

  • 521

    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介绍了Manifests . 但它们的问题是无法表示很多有用的类型,比如路径依赖类型:

    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
    

    因此,它们被_1566385替换,它们更易于使用并且很好地集成到新的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
    

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

    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)
    

    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]
    

    如上所述,他们不关心类型擦除,因此如果需要"full"类型 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
        ???
      }
    }
    

    如果用 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 Macros: “cannot create TypeTag from a type T having unresolved type parameters”

    Scala的官方文档站点还包含guide for Reflection .

相关问题