如何手动创建TypeTag?

时间:2012-07-15 19:11:02

标签: scala

我有兴趣手动创建一个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会产生

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

它似乎完全是编译器魔术,因为类型是硬编码的。不过还有办法手动完成吗?

4 个答案:

答案 0 :(得分:10)

在M3中,你可以用一种非常简单的方式创建一个类型标签,例如:TypeTag[Int](TypeRef(<scala package>, <symbol of scala.Int>, Nil))。它基本上意味着一旦创建了一个类型标记,它就会永远绑定到某个类加载器(即上面例子中用于加载scala.Int符号的类加载器)。

当时它很好,因为我们认为我们可以拥有一个适合所有类加载器的一体适用的镜像。这很方便,因为您可以编写implicitly[TypeTag[T]]并且编译器将使用该全局镜像来实例化您请求的类型。

不幸的是,根据反馈,我们意识到这不会起作用,我们需要多个镜像 - 每个镜像都有自己的类加载器。

然后很明显类型标签需要灵活,因为一旦你写了implicitly[TypeTag[T]],编译器就没有足够的信息你想要使用哪个类加载器。基本上有两种选择:1)使类型标记路径依赖于镜像(因此每次从编译器请求类型标记时都会强制显式提供镜像),2)将类型标记转换为类型工厂,能够在任何镜子中实例化自己长话短说,第一个选项没有用,所以我们就在这里。

因此,如https://github.com/scala/scala/blob/master/src/library/scala/reflect/base/TypeTags.scala#L143所述,目前需要以相当迂回的方式创建类型标签。您调用随附TypeTag中定义的工厂方法并提供两个参数:1)默认镜像,其中将实例化类型标记,2)可以在任意镜像中实例化类型标记的类型工厂。这基本上就是编译器在上面附带的打印输出中所做的。

为什么我打电话给这个环形交叉路口?因为要提供类型工厂,您需要手动子类化scala.reflect.base.TypeFactory类。这是函数和依赖类型方法之间边界上Scala类型系统的一个不幸的限制。

答案 1 :(得分:8)

基于Get TypeTag[A] from Class[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]
    }
  })
}

例如,这可用于获取另一个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)
}

这适用于Scala 2.10.2:

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

只要您没有多个Mirror,与特定ClassLoader绑定的限制可能不是问题。

答案 2 :(得分:5)

功能定义

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

只是另一种写作方式

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

表示编译器隐式创建标记实例。这背后的原因是Scala编译器通过硬编码其他擦除类型信息来解决JVM的类型擦除问题。

如果您不熟悉类型擦除问题,那就是JVM不存储类型参数信息,例如JVM将Seq[Set[Int]]类型简称为Seq[_]并且你无法用反射找出丢失的类型信息。

答案 3 :(得分:5)

目前,有三种方法可以手动创建支持通用的TypeTag 。前两个取决于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[_]]
}

如果您需要可序列化TypeTag并且性能不是您的主要关注点,那么第一种方法就是最简洁的。 OTOH,如果您的用例需要非常高效,请注意即时编译可能需要几秒钟才能完成。

第二种方法表现更好:

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

第二种模式创建一个类型化树并获取TypeTag中标记的具体类型,即,如果您的目标是List[String],它将被翻译为scala.collection.immutable.List[String],因为{ {1}}只是一个类型别名。这实际上是scala.List预期的行为。

最后一种方法是手动创建typeTag[List[String]]并使用它来创建Type。此方法容易出错,类型检查有限,并且使用内部API。但是,这是手动获取TypeTag的最快方法。

TypeTag