2012-07-15 18 views
9

estoy interesado en crear un TypeTag manualmente (desde 2.10M5):¿Cómo crear un TypeTag manualmente?

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 resultados en

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

Parece ser mágica por completo compilador debido a que los tipos están codificados. Sin embargo, ¿hay alguna manera de hacerlo manualmente?

Respuesta

10

En M3 puede crear una etiqueta de tipo de una manera muy simple, por ejemplo: TypeTag[Int](TypeRef(<scala package>, <symbol of scala.Int>, Nil)). Básicamente significaba que una vez que se crea una etiqueta de tipo, está ligada para siempre a un determinado cargador de clases (es decir, el que se usó para cargar un símbolo de scala.Int en el ejemplo anterior).

En aquel momento estaba bien, porque consideramos que podíamos tener un espejo de una sola talla que se adapta a todos los cargadores de clases. Eso fue conveniente, porque podría escribir implicitly[TypeTag[T]] y el compilador usaría ese espejo global para crear una instancia de un tipo que solicitó.

Lamentablemente, más tarde, en función de los comentarios recibidos, nos dimos cuenta de que esto no funcionaría y que necesitábamos múltiples espejos, cada uno con su propio cargador de clases.

Y luego se hizo evidente que las etiquetas de tipo deben ser flexibles, porque una vez que se escribe implicitly[TypeTag[T]], el compilador no tiene suficiente información sobre qué cargador de clases desea usar. Básicamente, había dos alternativas: 1) hacer que las etiquetas de tipo dependieran de la ruta en las réplicas (para que uno se viera obligado a proporcionar explícitamente una duplicación cada vez que se solicite una etiqueta de tipo desde el compilador), 2) transformar las etiquetas de tipo en fábricas de tipos, capaces de instanciarse a sí mismos en cualquier espejo. Para resumir, la primera opción no funcionó, entonces estamos donde estamos.

Por lo tanto, actualmente las etiquetas de tipo deben crearse de forma bastante indirecta, como se describe en https://github.com/scala/scala/blob/master/src/library/scala/reflect/base/TypeTags.scala#L143. Usted llama a un método de fábrica definido en el acompañante TypeTag y proporciona dos argumentos: 1) un espejo predeterminado, donde se creará una instancia de la etiqueta de tipo, 2) una fábrica de tipos que puede instanciar una etiqueta de tipo en espejo arbitrario. Esto es básicamente lo que hizo el compilador en la copia impresa que adjuntó anteriormente.

¿Por qué llamé a esta rotonda? Porque para proporcionar una fábrica de tipos, debe subclasificar manualmente la clase scala.reflect.base.TypeFactory. Esa es una limitación desafortunada del sistema de tipo Scala en el límite entre la función y los métodos de tipo dependiente.

+0

Veo, la Reflexión de Scala es mucho más complicada de lo que actualmente puedo pensar. Todavía tengo que pasar mucho tiempo hasta obtener lo que está pasando allí ... – sschaef

+0

¿Podría explicar su caso de uso? La construcción de etiquetas de tipo (y, en consecuencia, tipos de Scala) sobre la marcha, especialmente desde Java, se ve muy duro. Tal vez las etiquetas de clase (que llevan solo una clase Java, no un tipo Scala) serían suficientes? –

+0

No tengo un caso de uso;) Quería saber cómo funcionan las cosas y cómo usarlo/trabajar con él para su uso posterior ... – sschaef

5

La definición de la función

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

es sólo otra manera de escribir

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

lo que significa que la instancia de una etiqueta se crea de forma implícita por el compilador. El razonamiento detrás de esto es que el compilador de Scala resuelve el problema de borrado de tipo de la JVM al codificar de forma rígida la información del tipo borrado.

En caso de que usted no está familiarizado con el tema de tipo de borrado, es que la JVM no almacena la información de los parámetros de tipo, por ejemplo un tipo Seq[Set[Int]] sería visto simplemente como Seq[_] por la JVM y no habría ninguna manera de usted para descubrir la información del tipo que falta con la reflexión.

+0

No creo que esta respuesta es del todo cierto, porque hay una manera de crear manualmente manifiestos que son creados por el compilador de forma automática, también: 'nueva Manifiesto [Int] {def borrado = classof [Int]} '(esto ya no funciona en 2.10) – sschaef

+1

No estoy seguro de entender lo que está tratando de lograr aquí. Por supuesto, puede instanciar manualmente una clase 'TypeTag', pero su único propósito es ser instanciado detrás de la escena por el compilador, que genera la información dependiente del caso. Si, por cualquier motivo, intentas imitarlo, tu declaración decompiled 'private [this] val t: reflect.runtime.universe.TypeTag' muestra exactamente cómo puedes hacerlo, pero esa no es la forma en que esta clase estaba destinada a ser usado. –

+0

Un caso de uso para esto puede ser el uso de un método Scala que requiere una TypeTag desde el lado de Java. Pensé: tal vez haya una forma más fácil/distinta a la forma en que lo hace el compilador. – sschaef

8

Basado en 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] 
    } 
    }) 
} 

Por ejemplo, esto se puede utilizar para obtener el TypeTag para una parte de otro 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) 
} 

Esto funciona en Scala 2.10.2:

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

La limitación de estar atado a un particular Mirror es probable que no sea un problema, siempre y cuando no tenga múltiples ClassLoader s.

+1

Esto es exactamente lo que necesito. –

+1

Este método pierde la capacidad de serialización de la etiqueta: java.io.NotSerializableException: scala.reflect.runtime.JavaMirrors $ JavaMirror – user48956

+0

Estoy en Scala 2.10, por lo que nunca he visto TypeTags serializables. Debe ser increíble! :) –

1

Actualmente, hay tres formas de crear un TypeTagque admita genéricos manualmente. Los dos primeros depende de la scala-compiler, y con ellos se consiguen comprobación de tipos al igual que en tiempo de compilación, ya que es una compilación de código real:

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

Si necesita un rendimiento serializable TypeTag y no es su principal preocupación, el primer método es el más sucinto. OTOH, si su caso de uso debe ser muy eficiente, tenga cuidado de que la compilación sobre la marcha tarde unos segundos en terminar.

El segundo método realiza un poco mejor:

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

Este segundo modo crea un árbol escrita a máquina y obtiene los tipos concretos marcados en el TypeTag, es decir, si su objetivo es un List[String], será traducido a scala.collection.immutable.List[String], ya que scala.List es solo un alias de tipo. Ese es en realidad el comportamiento esperado de typeTag[List[String]].

El último método es crear un Type manualmente y usarlo para crear el TypeTag. Este método es propenso a errores, hay una verificación de tipo limitada y usa una API interna. Esta es, sin embargo, la forma más rápida de obtener manualmente un 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] 
    } 
    }) 
} 
Cuestiones relacionadas