web-dev-qa-db-fra.com

Scala: Qu'est-ce qu'un TypeTag et comment l'utiliser?

Tout ce que je sais sur TypeTags, c'est qu'ils ont en quelque sorte remplacé les Manifestes. Les informations sur Internet sont rares et ne me permettent pas de bien comprendre le sujet.

Je serais donc heureux si quelqu'un partageait un lien vers des matériaux utiles sur TypeTags, notamment des exemples et des cas d'utilisation populaires. Des réponses détaillées et des explications sont également les bienvenues.

354
Sergey Weiss

Une TypeTag résout le problème suivant: les types de Scala sont effacés au moment de l'exécution (effacement du type). Si on veut 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 aurons 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, 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] = [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

Ainsi, ils sont remplacés par TypeTags , qui sont à la fois beaucoup plus simples à utiliser et bien intégrés dans la nouvelle API de Reflection. Avec eux, nous pouvons résoudre le problème ci-dessus concernant les 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 que vous utilisiez =:= (égalité de types) et <:< (relation de sous-type) pour les contrôles 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 doit être fait car elle ne se soucie pas de choses telles que les préfixes (comme dans l’exemple).

Un TypeTag est complètement 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 tags:

ClassTag remplace ClassManifest alors que TypeTag 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 au moment de l'exécution (les types sont effacés):

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 s'intéressent pas à l'effacement des types. Par conséquent, si l'on veut des types "complets", TypeTag devrait être utilisé:

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 constater, la méthode tpe de TypeTag donne un Type complet, qui est identique à celui obtenu lorsque typeOf est appelée. Bien sûr, il est possible d'utiliser à la fois ClassTag et 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 question qui reste est maintenant quel est le sens de WeakTypeTag? En bref, TypeTag représente un type concret (cela ne permet que des types totalement instanciés) alors que WeakTypeTag ne permet que tout type. La plupart du temps, on se moque de ce qui est quoi (ce qui signifie que TypeTag devrait être utilisé), mais par exemple, lorsque des macros sont utilisées qui doivent 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 généré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, veuillez vous reporter à la question suivante: Macros Scala: "Impossible de créer un TypeTag à partir d'un type T dont les paramètres de type ne sont pas résolus"

Le site de documentation officiel de Scala contient également un guide de réflexion .

549
kiritsuku