简体   繁体   English

如何手动创建TypeTag?

[英]How to create a TypeTag manually?

I'm interested in creating a TypeTag manually (since 2.10M5): 我有兴趣手动创建一个TypeTag(从2.10M5开始):

object X {
  import reflect.runtime.universe._
  def tt[A : TypeTag](a: A) = typeTag[A] // how to do this manually?
  val t = tt(List("")(_))
}

scalac -Xprint:typer <file>.scala results in scalac -Xprint:typer <file>.scala导致

package <empty> {
  object X extends scala.AnyRef {
    def <init>(): X.type = {
      X.super.<init>();
      ()
    };
    import scala.reflect.runtime.`package`.universe._;
    def tt[A >: Nothing <: Any](a: A)(implicit evidence$1: reflect.runtime.universe.TypeTag[A]): reflect.runtime.universe.TypeTag[A] = scala.reflect.runtime.`package`.universe.typeTag[A](evidence$1);
    private[this] val t: reflect.runtime.universe.TypeTag[Int => String] = X.this.tt[Int => String](((x$1: Int) => immutable.this.List.apply[String]("").apply(x$1)))({
      val $u: reflect.runtime.universe.type = scala.this.reflect.runtime.`package`.universe;
      val $m: $u.Mirror = scala.this.reflect.runtime.`package`.universe.runtimeMirror(this.getClass().getClassLoader());
      $u.TypeTag.apply[Int => String]($m, {
        final class $typecreator1 extends TypeCreator {
          def <init>(): $typecreator1 = {
            $typecreator1.super.<init>();
            ()
          };
          def apply[U >: Nothing <: scala.reflect.base.Universe with Singleton]($m$untyped: scala.reflect.base.MirrorOf[U]): U#Type = {
            val $u: U = $m$untyped.universe;
            val $m: $u.Mirror = $m$untyped.asInstanceOf[$u.Mirror];
            $u.TypeRef.apply($u.ThisType.apply($m.staticModule("scala").asModuleSymbol.moduleClass), $m.staticClass("scala.Function1"), scala.collection.immutable.List.apply[$u.Type]($m.staticClass("scala.Int").asTypeSymbol.asTypeConstructor, $m.staticClass("java.lang.String").asTypeSymbol.asTypeConstructor))
          }
        };
        new $typecreator1()
      })
    });
    <stable> <accessor> def t: reflect.runtime.universe.TypeTag[Int => String] = X.this.t
  }
}

It seems to be completely compiler magic because the types are hardcoded. 它似乎完全是编译器魔术,因为类型是硬编码的。 Nevertheless is there a way to do this manually? 不过有没有办法手动执行此操作?

In M3 you could create a type tag in a very simple way, eg: TypeTag[Int](TypeRef(<scala package>, <symbol of scala.Int>, Nil)) . 在M3中,您可以以非常简单的方式创建类型标记,例如: TypeTag[Int](TypeRef(<scala package>, <symbol of scala.Int>, Nil)) It basically meant that once a type tag is created, it is forever bound to a certain classloader (namely the one that was used to load a symbol of scala.Int in the example above). 它基本上意味着一旦创建了一个类型标记,它就会永远绑定到某个类加载器(即上面示例中用于加载scala.Int符号的类加载器)。

Back then it was fine, because we considered that we could have a one-size-fits-all mirror that accomodates all classloaders. 当时很好,因为我们认为我们可以拥有一个适合所有类加载器的一体适用的镜像。 That was convenient, because you could just write implicitly[TypeTag[T]] and the compiler would use that global mirror to instantiate a Type you requested. 这很方便,因为您可以implicitly[TypeTag[T]]编写implicitly[TypeTag[T]] ,编译器将使用该全局镜像来实例化您请求的Type。

Unfortunately later, based on feedback, we realized that this isn't going to work, and that we need multiple mirrors - each having its own classloader. 不幸的是,根据反馈,我们意识到这不会起作用,我们需要多个镜像 - 每个镜像都有自己的类加载器。

And then it became apparent that type tags need to be flexible, because once you write that implicitly[TypeTag[T]] the compiler doesn't have enough information what classloader you want to use. 然后很明显类型标签需要灵活,因为一旦你implicitly[TypeTag[T]]编写implicitly[TypeTag[T]] ,编译器就没有足够的信息你想要使用哪个类加载器。 Basically there were two alternatives: 1) make type tags path-dependent on mirrors (so that one would be forced to explicitly provide a mirror every time a type tag is requested from the compiler), 2) transform type tags into type factories, capable of instantiating themselves in any mirror. 基本上有两种选择:1)使类型标记路径依赖于镜像(因此每次从编译器请求类型标记时都会强制显式提供镜像),2)将类型标记转换为类型工厂,能够在任何镜子中实例化自己 Long story short, the first option didn't work, so we are where we are. 长话短说,第一个选项没有用,所以我们就在这里。

So currently type tags need to be created in quite a roundabout way, as outlined in https://github.com/scala/scala/blob/master/src/library/scala/reflect/base/TypeTags.scala#L143 . 因此,如https://github.com/scala/scala/blob/master/src/library/scala/reflect/base/TypeTags.scala#L143中所述,目前类型标签需要以相当迂回的方式创建。 You call a factory method defined in the companion TypeTag and provide two arguments: 1) a default mirror, where the type tag will be instantiated, 2) a type factory that can instantiate a type tag in arbitrary mirror. 您调用随附的TypeTag定义的工厂方法并提供两个参数:1)默认镜像,其中将实例化类型标记,2)可以在任意镜像中实例化类型标记的类型工厂。 This is basically what the compiler did in the printout you've attached above. 这基本上就是编译器在你上面附带的打印输出中所做的。

Why I called this roundabout? 为什么我打电话给这个环岛? Because to provide a type factory you need to manually subclass the scala.reflect.base.TypeFactory class. 因为要提供类型工厂,您需要手动子类化scala.reflect.base.TypeFactory类。 That's an unfortunate limitation of Scala type system on the border between function and dependently-typed methods. 这是Scala类型系统在函数和依赖类型方法之间的边界上的一个不幸的限制。

Based on Get TypeTag[A] from Class[A] : 基于类[A]中的Get TypeTag [A]

import scala.reflect.runtime.universe._

def typeToTypeTag[T](
  tpe: Type,
  mirror: reflect.api.Mirror[reflect.runtime.universe.type]
): TypeTag[T] = {
  TypeTag(mirror, new reflect.api.TypeCreator {
    def apply[U <: reflect.api.Universe with Singleton](m: reflect.api.Mirror[U]) = {
      assert(m eq mirror, s"TypeTag[$tpe] defined in $mirror cannot be migrated to $m.")
      tpe.asInstanceOf[U#Type]
    }
  })
}

For example this can be used to get the TypeTag for a part of another TypeTag : 例如,这可以用来获得TypeTag另外一部分TypeTag

def inside[A, B](tag: TypeTag[(A, B)]): (TypeTag[A], TypeTag[B]) = {
  val tpes = tag.tpe.asInstanceOf[TypeRefApi].args
  val tagA = typeToTypeTag[A](tpes(0), tag.mirror)
  val tagB = typeToTypeTag[B](tpes(1), tag.mirror)
  return (tagA, tagB)
}

This works in Scala 2.10.2: 这适用于Scala 2.10.2:

scala> inside(typeTag[(Int, Double)])
res0: (reflect.runtime.universe.TypeTag[Int], reflect.runtime.universe.TypeTag[Double]) = (TypeTag[Int],TypeTag[Double])

The limitation of being tied to a particular Mirror is likely not a problem as long as you don't have multiple ClassLoader s. 只要您没有多个ClassLoader ,绑定到特定Mirror的限制可能不是问题。

Currently, there are three ways to create a TypeTag that supports generics manually. 目前,有三种方法可以创建一个手动支持泛型TypeTag The first two depends on the scala-compiler , and with them you get type checking just like in compile time, as it is an actual code compilation : 前两个依赖于scala-compiler ,并且使用它们就像编译时一样进行类型检查, 因为它是一个实际的代码编译

import scala.reflect.runtime.universe._
import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox

val toolbox = currentMirror.mkToolBox()

def createTypeTag(tp: String): TypeTag[_] = {
  val ttree = toolbox.parse(s"scala.reflect.runtime.universe.typeTag[$tp]")
  toolbox.eval(ttree).asInstanceOf[TypeTag[_]]
}

If you need a serializable TypeTag and performance isn't your main concern, the first method is the most succinct. 如果您需要可序列化的TypeTag并且性能不是您主要关心的问题,那么第一种方法就是最简洁的方法。 OTOH, if your use case needs to be very performant, beware that on-the-fly compilation might take a couple of seconds to finish. OTOH,如果你的用例需要非常高效,请注意即时编译可能需要几秒钟才能完成。

The second method performs a little better: 第二种方法表现得更好:

def createTypeTag(tp: String): TypeTag[_] = {
  val ttagCall = s"scala.reflect.runtime.universe.typeTag[$tp]"
  val tpe = toolbox.typecheck(toolbox.parse(ttagCall), toolbox.TYPEmode).tpe.resultType.typeArgs.head

  TypeTag(currentMirror, new reflect.api.TypeCreator {
    def apply[U <: reflect.api.Universe with Singleton](m: reflect.api.Mirror[U]) = {
      assert(m eq mirror, s"TypeTag[$tpe] defined in $mirror cannot be migrated to $m.")
      tpe.asInstanceOf[U#Type]
    }
  }
}

This second mode creates a typed tree and fetches the concrete types marked in the TypeTag , ie, if your target is a List[String] , it will be translated to scala.collection.immutable.List[String] , since scala.List is only a type alias. 第二种模式创建一个类型化的树并获取在TypeTag标记的具体类型,即,如果您的目标是List[String] ,它将被转换为scala.collection.immutable.List[String] ,因为scala.List是只是一个类型别名。 That's actually the behavior expected from typeTag[List[String]] . 这实际上是typeTag[List[String]]期望的行为。

The last method is to create a Type manually and use it to create the TypeTag . 最后一种方法是手动创建一个Type并使用它来创建TypeTag This method is error-prone, there is limited type checking, and it uses internal API. 此方法容易出错,类型检查有限,并且使用内部API。 This is, however, the fastest way to manually obtain a TypeTag . 但是,这是手动获取TypeTag的最快方法。

def createTypeTag(tp: String): TypeTag[_] = {
  val typs = // ... manipulate the string to extract type and parameters
  val typSym = currentMirror.staticClass(typs[0])
  val paramSym = currentMirror.staticClass(typs[1])

  val tpe = universe.internal.typeRef(NoPrefix, typSym, List(paramSym.selfType))

  val ttag = TypeTag(currentMirror, new TypeCreator {
    override def apply[U <: Universe with Singleton](m: Mirror[U]): U#Type = {
      assert(m == currentMirror, s"TypeTag[$tpe] defined in $currentMirror cannot be migrated to $m.")
      tpe.asInstanceOf[U#Type]
    }
  })
}

The function definition 功能定义

def tt[A : TypeTag](a: A) = typeTag[A]

is just another way of writing 只是另一种写作方式

def tt(a: A)(implicit tag: TypeTag[A]) = tag

which means that the instance of a tag is being created implicitly by the compiler. 这意味着标记的实例是由编译器隐式创建的。 The reasoning behind this is that Scala compiler solves the JVM's type erasure problem by hardcoding the otherwise erased type information. 这背后的原因是Scala编译器通过硬编码其他擦除类型信息来解决JVM的类型擦除问题。

In case you're not familiar with type erasure issue, it is that JVM doesn't store the type parameters information, for instance a type Seq[Set[Int]] would be seen simply as Seq[_] by the JVM and there would be no way for you to find out the missing type information with reflection. 如果你不熟悉类型擦除问题,那就是JVM不存储类型参数信息,例如Seq[Set[Int]]会将Seq[Set[Int]]类型简单地看作Seq[_]你无法用反射找出缺失的类型信息。

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

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