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 .