2012-07-15 187 views
9

我感兴趣的是手动创建TypeTag(因为2.10M5):如何手动创建TypeTag?

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

这似乎是完全编译魔法,因为类型是固定的。不过有没有办法做到这一点手动?

回答

10

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

那时很好,因为我们认为我们可以有一个适合所有类加载器的万能镜。这很方便,因为您只需编写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类型系统在函数和依赖类型方法之间的边界的一个不幸的限制。

+0

我看到,Scala Reflection比我目前能想到的要复杂得多。我仍然需要花费很多时间,直到得到正在发生的事情... – sschaef 2012-07-16 12:50:05

+0

你能否详细说明你的用例?动态构建类型标记(以及因此的Scala类型),特别是从Java中构建类型标记,看起来非常硬。也许类标签(只携带Java类,而不是Scala类型)就足够了? – 2012-07-16 14:44:50

+0

我没有用例;)我想知道事情是如何工作的,以及如何使用它/用于以后使用... – sschaef 2012-07-16 14:51:17

5

函数定义

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

是写

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

的只是另一种方式,这意味着正由编译器隐式创建一个标签的实例。这背后的原因是Scala编译器通过硬编码擦除​​类型信息来解决JVM的类型擦除问题。

如果您不熟悉类型擦除问题,那就是JVM不存储类型参数信息,例如,类型Seq[Set[Int]]将被JVM看作Seq[_],并且将无法你可以通过反射找出遗漏的类型信息。

+0

我不认为这个答案是完全正确的,因为有一种方法可以手动创建舱单这是由编译器自动创建的,太:'新的舱单[INT] {高清擦除= classOf [INT]} '(这在2.10中不起作用) – sschaef 2012-07-15 21:36:12

+1

我不确定我是否理解你要在这里实现的目标。当然,你可以手动实例化一个TypeTag类,但它的唯一目的是在编译器后面实例化,这会生成与案例相关的信息。如果无论你试图模仿它的目的是什么,那么你的反编译的'private [this] val t:reflect.runtime.universe.TypeTag'声明就完全显示了你如何做到这一点,但是这不是这个类的目的使用。 – 2012-07-15 21:51:39

+0

用于此的用例可能是使用Scala方法,该方法需要Java端的TypeTag。我想:也许有比编译器更容易/另一种方式。 – sschaef 2012-07-15 22:19:34

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

这个作品在斯卡拉2.10.2:

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

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

+1

这正是我需要的。 – 2014-11-13 00:25:34

+1

此方法失去了标记被序列化的能力:java.io.NotSerializableException:scala.reflect.runtime.JavaMirrors $ JavaMirror – user48956 2016-08-23 01:58:42

+0

我在Scala 2.10上,所以我从来没有看到可序列化的TypeTags。一定很棒! :) – 2016-08-23 09:32:16

1

目前,有三种方式可以手动创建支持泛型的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],因为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] 
    } 
    }) 
}