12

我对手动创建 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

4 回答 4

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 类型系统在函数和依赖类型方法之间的边界上的不幸限制。

于 2012-07-16T12:06:59.247 回答
10

基于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只要您没有多个ClassLoaders ,绑定到特定的限制可能不是问题。

于 2014-09-05T17:26:03.997 回答
7

目前,手动创建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,即,如果您的目标是 a List[String],它将被转换为scala.collection.immutable.List[String],因为scala.List它只是一个类型别名。这实际上是预期的行为typeTag[List[String]]

最后一种方法是Type手动创建一个并使用它来创建TypeTag. 此方法容易出错,类型检查有限,并且使用内部 API。但是,这是手动获取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]
    }
  })
}
于 2017-06-21T03:19:44.790 回答
6

函数定义

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

只是另一种写作方式

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

这意味着标签的实例是由编译器隐式创建的。这背后的原因是 Scala 编译器通过硬编码原本被擦除的类型信息来解决 JVM 的类型擦除问题。

如果您不熟悉类型擦除问题,那是 JVM 不存储类型参数信息,例如类型Seq[Set[Int]]将被简单地视为Seq[_]JVM,您将无法找出丢失的用反射输入信息。

于 2012-07-15T21:34:02.853 回答