A TypeTag
löst das Problem, dass Scalas Typen zur Laufzeit gelöscht werden (Typlöschung). Wenn wir wollen
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"
}
Wir werden Warnungen bekommen:
<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"
^
Um dieses Problem zu lösen, wurden Manifeste in Scala eingeführt. Sie haben jedoch das Problem, dass sie nicht in der Lage sind, viele nützliche Typen wie pfadabhängige Typen darzustellen:
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
Daher werden sie durch TypeTags ersetzt , die sowohl viel einfacher zu verwenden als auch gut in die neue Reflection-API integriert sind. Mit ihnen können wir das obige Problem über pfadabhängige Typen elegant lösen:
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
Sie sind auch einfach zu verwenden, um Typparameter zu überprüfen:
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
An dieser Stelle ist es äußerst wichtig zu verstehen, wie =:=
(Typgleichheit) und <:<
(Subtypbeziehung) für Gleichheitsprüfungen verwendet werden. Verwenden Sie niemals ==
oder !=
, es sei denn, Sie wissen genau, was Sie tun:
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
Letzterer prüft die strukturelle Gleichheit, was häufig nicht der Fall ist, da Dinge wie Präfixe (wie im Beispiel) nicht berücksichtigt werden.
A TypeTag
ist vollständig vom Compiler generiert, dh der Compiler erstellt und füllt a aus, TypeTag
wenn man eine Methode aufruft, die eine solche erwartet TypeTag
. Es gibt drei verschiedene Arten von Tags:
ClassTag
ersetzt, ClassManifest
während TypeTag
mehr oder weniger der Ersatz für ist Manifest
.
Ersteres ermöglicht die vollständige Arbeit mit generischen Arrays:
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
stellt nur die Informationen bereit, die zum Erstellen von Typen zur Laufzeit erforderlich sind (die vom Typ gelöscht werden):
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]
Wie man oben sehen kann, kümmern sie sich nicht um das Löschen von Typen. Wenn man also "vollständige" Typen möchte, TypeTag
sollte Folgendes verwendet werden:
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
Wie kann man das Verfahren sieht tpe
von TypeTag
Ergebnissen in einem vollständigen Type
, die gleich ich ist erhalten , wenn typeOf
aufgerufen wird. Natürlich ist es möglich, beide zu verwenden, ClassTag
und 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]])
Die verbleibende Frage ist nun, was ist der Sinn von WeakTypeTag
? Kurz gesagt, TypeTag
stellt einen konkreten Typ dar (dies bedeutet, dass nur vollständig instanziierte Typen zulässig sind), während WeakTypeTag
nur ein beliebiger Typ zulässig ist . Meistens ist es egal, welches was ist (was bedeutet TypeTag
, verwendet werden sollte), aber wenn beispielsweise Makros verwendet werden, die mit generischen Typen funktionieren sollen, werden sie benötigt:
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
???
}
}
Wenn man WeakTypeTag
durch TypeTag
einen Fehler ersetzt wird geworfen:
<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]
^
Für eine detailliertere Erklärung der Unterschiede zwischen TypeTag
und WeakTypeTag
siehe diese Frage: Scala-Makros: "TypeTag kann nicht aus einem Typ T mit ungelösten Typparametern erstellt werden."
Die offizielle Dokumentationsseite von Scala enthält auch einen Leitfaden für Reflection .