A TypeTag
résout le problème de l'effacement des types de Scala au moment de l'exécution (effacement de type). Si nous voulons faire
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"
}
nous recevrons des avertissements:
<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"
^
Pour résoudre ce problème, des manifestes ont été introduits dans Scala. Mais ils ont le problème de ne pas pouvoir représenter beaucoup de types utiles, comme les types dépendants du chemin:
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] = Foo@681e731c.type#Foo$Bar
scala> val ev2 = m(f2)(b2)
warning: there were 2 deprecation warnings; re-run with -deprecation for details
ev2: Manifest[f2.Bar] = Foo@3e50039c.type#Foo$Bar
scala> ev1 == ev2 // they should be different, thus the result is wrong
res28: Boolean = true
Ainsi, ils sont remplacés par TypeTags , qui sont à la fois beaucoup plus simples à utiliser et bien intégrés dans la nouvelle API Reflection. Avec eux, nous pouvons résoudre le problème ci-dessus à propos des types dépendants du chemin avec élégance:
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
Ils sont également faciles à utiliser pour vérifier les paramètres de type:
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
À ce stade, il est extrêmement important de comprendre l'utilisation de =:=
(égalité de type) et <:<
(relation de sous-type) pour les vérifications d'égalité. N'utilisez jamais ==
ou !=
, sauf si vous savez absolument ce que vous faites:
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
Ce dernier vérifie l'égalité structurelle, ce qui n'est souvent pas ce qui devrait être fait car il ne se soucie pas de choses telles que les préfixes (comme dans l'exemple).
A TypeTag
est entièrement généré par le compilateur, ce qui signifie que le compilateur crée et remplit un TypeTag
quand on appelle une méthode qui attend un tel TypeTag
. Il existe trois formes différentes de balises:
ClassTag
substituts ClassManifest
alors que TypeTag
c'est plus ou moins le remplacement de Manifest
.
Le premier permet de travailler pleinement avec des tableaux génériques:
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
fournit uniquement les informations nécessaires pour créer des types lors de l'exécution (dont le type est effacé):
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]
Comme on peut le voir ci-dessus, ils ne se soucient pas de l'effacement des types, donc si l'on veut que les types "complets" TypeTag
soient utilisés:
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
Comme on peut le voir, la méthode tpe
des TypeTag
résultats dans un full Type
, qui est la même que nous obtenons quand typeOf
est appelée. Bien sûr, il est possible d'utiliser les deux, ClassTag
etTypeTag
:
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 question restante est maintenant quel est le sens de WeakTypeTag
? En bref, TypeTag
représente un type concret (cela signifie qu'il n'autorise que les types entièrement instanciés) alors qu'il WeakTypeTag
ne permet que n'importe quel type. La plupart du temps, on ne se soucie pas de savoir quoi (quels moyens TypeTag
doivent être utilisés), mais par exemple, lorsque des macros sont utilisées qui devraient fonctionner avec des types génériques, elles sont nécessaires:
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 l'on remplace WeakTypeTag
par TypeTag
une erreur est levée:
<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]
^
Pour une explication plus détaillée des différences entre TypeTag
et WeakTypeTag
voir cette question: Macros Scala: "ne peut pas créer TypeTag à partir d'un type T ayant des paramètres de type non résolus"
Le site officiel de documentation de Scala contient également un guide pour Reflection .