scala types scala-2.10 reification

Scala: ¿Qué es un TypeTag y cómo lo uso?



types scala-2.10 (1)

Todo lo que sé sobre TypeTags es que de alguna manera reemplazaron a Manifests. La información en Internet es escasa y no me proporciona un buen sentido del tema.

Así que me alegraría que alguien compartiera un enlace a algunos materiales útiles sobre TypeTags, incluidos ejemplos y casos de uso populares. Respuestas detalladas y explicaciones también son bienvenidas.


Un TypeTag resuelve el problema de que los tipos de Scala se borran en tiempo de ejecución (borrado de tipo). Si queremos hacer

class Foo class Bar extends Foo def meth[A](xs: List[A]) = xs match { case _: List[String] => "list of strings" case _: List[Foo] => "list of foos" }

obtendremos advertencias:

<console>:23: warning: non-variable type argument String in type pattern List[String]↩ is unchecked since it is eliminated by erasure case _: List[String] => "list of strings" ^ <console>:24: warning: non-variable type argument Foo in type pattern List[Foo]↩ is unchecked since it is eliminated by erasure case _: List[Foo] => "list of foos" ^

Para resolver este problema, los Manifests se introdujeron en Scala. Pero tienen el problema de no poder representar muchos tipos útiles, como los tipos dependientes de la ruta:

scala> class Foo{class Bar} defined class Foo scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev warning: there were 2 deprecation warnings; re-run with -deprecation for details m: (f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar])Manifest[f.Bar] scala> val f1 = new Foo;val b1 = new f1.Bar f1: Foo = Foo@681e731c b1: f1.Bar = Foo$Bar@271768ab scala> val f2 = new Foo;val b2 = new f2.Bar f2: Foo = Foo@3e50039c b2: f2.Bar = Foo$Bar@771d16b9 scala> val ev1 = m(f1)(b1) warning: there were 2 deprecation warnings; re-run with -deprecation for details ev1: Manifest[f1.Bar] = [email protected]#Foo$Bar scala> val ev2 = m(f2)(b2) warning: there were 2 deprecation warnings; re-run with -deprecation for details ev2: Manifest[f2.Bar] = [email protected]#Foo$Bar scala> ev1 == ev2 // they should be different, thus the result is wrong res28: Boolean = true

Por lo tanto, son reemplazados por TypeTags , que son mucho más TypeTags de usar y están bien integrados en la nueva API de Reflection. Con ellos podemos resolver el problema anterior sobre los tipos dependientes de la ruta de forma elegante:

scala> def m(f: Foo)(b: f.Bar)(implicit ev: TypeTag[f.Bar]) = ev m: (f: Foo)(b: f.Bar)(implicit ev: reflect.runtime.universe.TypeTag[f.Bar])↩ reflect.runtime.universe.TypeTag[f.Bar] scala> val ev1 = m(f1)(b1) ev1: reflect.runtime.universe.TypeTag[f1.Bar] = TypeTag[f1.Bar] scala> val ev2 = m(f2)(b2) ev2: reflect.runtime.universe.TypeTag[f2.Bar] = TypeTag[f2.Bar] scala> ev1 == ev2 // the result is correct, the type tags are different res30: Boolean = false scala> ev1.tpe =:= ev2.tpe // this result is correct, too res31: Boolean = false

También son fáciles de usar para verificar los parámetros de tipo:

import scala.reflect.runtime.universe._ def meth[A : TypeTag](xs: List[A]) = typeOf[A] match { case t if t =:= typeOf[String] => "list of strings" case t if t <:< typeOf[Foo] => "list of foos" } scala> meth(List("string")) res67: String = list of strings scala> meth(List(new Bar)) res68: String = list of foos

En este punto, es extremadamente importante entender usar =:= (igualdad de tipo) y <:< (relación de subtipo) para verificaciones de igualdad. Nunca use == o != , A menos que sepa absolutamente lo que hace:

scala> typeOf[List[java.lang.String]] =:= typeOf[List[Predef.String]] res71: Boolean = true scala> typeOf[List[java.lang.String]] == typeOf[List[Predef.String]] res72: Boolean = false

Este último verifica la igualdad estructural, que a menudo no es lo que se debe hacer porque no le interesan cosas como los prefijos (como en el ejemplo).

Un TypeTag está completamente generado por el compilador, lo que significa que el compilador crea y llena un TypeTag cuando se llama a un método que espera un TypeTag . Existen tres formas diferentes de etiquetas:

ClassTag sustituye a ClassManifest mientras que TypeTag es más o menos el reemplazo de Manifest .

El primero permite trabajar completamente con arreglos genéricos:

scala> import scala.reflect._ import scala.reflect._ scala> def createArr[A](seq: A*) = Array[A](seq: _*) <console>:22: error: No ClassTag available for A def createArr[A](seq: A*) = Array[A](seq: _*) ^ scala> def createArr[A : ClassTag](seq: A*) = Array[A](seq: _*) createArr: [A](seq: A*)(implicit evidence$1: scala.reflect.ClassTag[A])Array[A] scala> createArr(1,2,3) res78: Array[Int] = Array(1, 2, 3) scala> createArr("a","b","c") res79: Array[String] = Array(a, b, c)

ClassTag proporciona solo la información necesaria para crear tipos en tiempo de ejecución (que son borrados):

scala> classTag[Int] res99: scala.reflect.ClassTag[Int] = ClassTag[int] scala> classTag[Int].runtimeClass res100: Class[_] = int scala> classTag[Int].newArray(3) res101: Array[Int] = Array(0, 0, 0) scala> classTag[List[Int]] res104: scala.reflect.ClassTag[List[Int]] =↩ ClassTag[class scala.collection.immutable.List]

Como se puede ver arriba, a ellos no les importa el borrado de tipo, por lo tanto, si uno quiere tipos "completos", debe usar TypeTag :

scala> typeTag[List[Int]] res105: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[scala.List[Int]] scala> typeTag[List[Int]].tpe res107: reflect.runtime.universe.Type = scala.List[Int] scala> typeOf[List[Int]] res108: reflect.runtime.universe.Type = scala.List[Int] scala> res107 =:= res108 res109: Boolean = true

Como se puede ver, el método tpe de TypeTag da TypeTag resultado un Type completo, que es el mismo que obtenemos cuando se llama a typeOf . Por supuesto, es posible usar ambos, ClassTag y TypeTag :

scala> def m[A : ClassTag : TypeTag] = (classTag[A], typeTag[A]) m: [A](implicit evidence$1: scala.reflect.ClassTag[A],↩ implicit evidence$2: reflect.runtime.universe.TypeTag[A])↩ (scala.reflect.ClassTag[A], reflect.runtime.universe.TypeTag[A]) scala> m[List[Int]] res36: (scala.reflect.ClassTag[List[Int]],↩ reflect.runtime.universe.TypeTag[List[Int]]) =↩ (scala.collection.immutable.List,TypeTag[scala.List[Int]])

La pregunta que queda ahora es ¿cuál es el sentido de WeakTypeTag ? En resumen, TypeTag representa un tipo concreto (esto significa que solo permite tipos totalmente instanciados) mientras que WeakTypeTag solo permite cualquier tipo. La mayoría de las veces a uno no le importa qué es qué (lo que significa que se debe usar TypeTag ), pero, por ejemplo, cuando se usan macros que deberían funcionar con tipos genéricos, se necesitan:

object Macro { import language.experimental.macros import scala.reflect.macros.Context def anymacro[A](expr: A): String = macro __anymacro[A] def __anymacro[A : c.WeakTypeTag](c: Context)(expr: c.Expr[A]): c.Expr[A] = { // to get a Type for A the c.WeakTypeTag context bound must be added val aType = implicitly[c.WeakTypeTag[A]].tpe ??? } }

Si uno reemplaza WeakTypeTag con TypeTag un error:

<console>:17: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context)(expr: c.Expr[A]): c.Expr[String] found : (c: scala.reflect.macros.Context)(expr: c.Expr[A])(implicit evidence$1: c.TypeTag[A]): c.Expr[A] macro implementations cannot have implicit parameters other than WeakTypeTag evidences def anymacro[A](expr: A): String = macro __anymacro[A] ^

Para obtener una explicación más detallada sobre las diferencias entre TypeTag y WeakTypeTag consulte esta pregunta: Scala Macros: "no se puede crear TypeTag a partir de un tipo T que tiene parámetros de tipo no resueltos"

El sitio oficial de documentación de Scala también contiene una guía para la reflexión .