Scala: Co to jest TypeTag i jak go używać?


361

Wiem tylko o TypeTags, że w jakiś sposób zastąpiły Manifesty. Informacje w Internecie są rzadkie i nie dają mi dobrego zrozumienia tematu.

Byłbym szczęśliwy, gdyby ktoś udostępnił link do przydatnych materiałów na TypeTags, w tym przykładów i popularnych przypadków użycia. Mile widziane są także szczegółowe odpowiedzi i wyjaśnienia.


1
W poniższym artykule z dokumentacji Scali opisano zarówno, jak i dlaczego znaczników typu, a także sposób ich użycia w kodzie: docs.scala-lang.org/overviews/reflection/…
btiernay

Odpowiedzi:


563

TypeTagRozwiązuje problem, że typy Scala są usunięte w czasie wykonywania (typ skasowaniem). Jeśli chcemy

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"
}

otrzymamy ostrzeżenia:

<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"
                 ^

Aby rozwiązać ten problem, do Scali wprowadzono manifesty . Ale problem polega na tym, że nie mogą reprezentować wielu użytecznych typów, takich jak typy zależne od ścieżki:

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

Dlatego są one zastępowane przez TypeTags , które są zarówno znacznie prostsze w użyciu, jak i dobrze zintegrowane z nowym interfejsem API Reflection. Dzięki nim możemy elegancko rozwiązać powyższy problem dotyczący typów zależnych od ścieżki:

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

Są również łatwe w użyciu, aby sprawdzić parametry typu:

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

W tym momencie niezwykle ważne jest zrozumienie używania =:=(równości typów) i <:<(relacji podtypów) do kontroli równości. Nigdy nie używaj ==lub !=, chyba że absolutnie wiesz, co robisz:

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

Ten ostatni sprawdza równość strukturalną, co często nie jest tym, co należy zrobić, ponieważ nie przejmuje się takimi rzeczami, jak przedrostki (jak w przykładzie).

A TypeTagjest całkowicie generowany przez kompilator, co oznacza, że ​​kompilator tworzy i wypełnia a, TypeTaggdy wywołuje metodę oczekującą takiego TypeTag. Istnieją trzy różne formy tagów:

ClassTagzastępuje, ClassManifestpodczas gdy TypeTagmniej więcej zastępuje Manifest.

Ten pierwszy pozwala na pełną pracę z rodzajowymi tablicami:

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 zawiera tylko informacje potrzebne do tworzenia typów w czasie wykonywania (które są usuwane z typów):

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]

Jak widać powyżej, nie przejmują się usuwaniem typów, dlatego jeśli chcemy mieć „pełne” typy, TypeTagnależy użyć:

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

Jak widać, metoda tpez TypeTagwynikami w pełnym Type, która jest taka sama otrzymujemy gdy typeOfjest tzw. Oczywiście możliwe jest użycie zarówno, jak ClassTagi 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]])

Pozostaje pytanie, jakie jest sens WeakTypeTag? Krótko mówiąc, TypeTagreprezentuje konkretny typ (oznacza to, że zezwala tylko na typy w pełni tworzone), a WeakTypeTagpo prostu zezwala na dowolny typ. TypeTagPrzez większość czasu nie dba się o to, co jest (co oznacza, że należy użyć), ale na przykład, gdy używane są makra, które powinny działać z typami rodzajowymi, są one potrzebne:

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
    ???
  }
}

Jeśli jeden zamienia WeakTypeTagsię, TypeTaggenerowany jest błąd:

<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]
                                                      ^

Aby uzyskać bardziej szczegółowe wyjaśnienie różnic między TypeTagi WeakTypeTagzobacz to pytanie: Makra Scala: „nie można utworzyć znacznika TypeTag z typu T o nierozwiązanych parametrach typu”

Oficjalna strona dokumentacji Scali zawiera również przewodnik do Refleksji .


19
Dzięki za odpowiedź! Niektóre komentarze: 1) ==dla typów reprezentuje równość strukturalną, a nie równość odniesienia. =:=wziąć pod ekwiwalencyjne typ konta (nawet bez oczywistych, takich jak ekwiwalencyjne przedrostków, które pochodzą z różnych lustrach), 2) Zarówno TypeTagi AbsTypeTagsą oparte na lustrach. Różnica polega na tym, że TypeTagzezwala tylko na typy w pełni tworzone (tj. Bez żadnych parametrów typu lub odniesień do elementów typu abstrakcyjnego), 3) Szczegółowe wyjaśnienie znajduje się tutaj: stackoverflow.com/questions/12093752
Eugene Burmako

10
4) Manifesty mają problem z niemożnością reprezentowania wielu użytecznych typów. Zasadniczo mogą wyrażać tylko odwołania do typów (typy zwykłe, takie jak Inti typy ogólne, takie jak List[Int]), pomijając takie typy Scala, jak np. Udoskonalenia, typy zależne od ścieżki, elementy egzystencjalne, typy z adnotacjami. Manifestacje również są na wyciągnięcie ręki, więc nie mogą korzystać z ogromnej wiedzy, którą kompilator ma do powiedzenia, powiedzmy, obliczenia linearyzacji typu, sprawdzenia, czy jeden typ jest
podtypem

9
5) Aby tagi typu kontrastu nie były „lepiej zintegrowane”, są po prostu zintegrowane z nowym interfejsem API odbicia (w przeciwieństwie do manifestów, które nie są zintegrowane z niczym). Zapewnia to dostęp do znaczników typów do niektórych aspektów kompilatora, np. Do Types.scala(7kloc kodu, który wie, w jaki sposób obsługiwane są typy), Symbols.scala(3kloc kodu, który wie, jak działają tabele symboli) itp.
Eugene Burmako

9
6) ClassTagjest dokładnym zamiennikiem ClassManifest, podczas gdy TypeTagmniej więcej zastępuje Manifest. Mniej więcej dlatego, że: 1) znaczniki typu nie niosą wymazań, 2) manifesty to duży hack, a zrezygnowaliśmy z emulowania jego zachowania za pomocą znaczników typu. # 1 można naprawić za pomocą obwiedni kontekstu ClassTag i TypeTag, gdy potrzebujesz zarówno wymazań, jak i typów, a zwykle nie przejmuje się tym # 2, ponieważ możliwe jest wyrzucenie wszystkich hacków i użycie pełnoprawnego API odbicia zamiast.
Eugene Burmako,

11
Naprawdę mam nadzieję, że kompilator Scala w pewnym momencie pozbynie się przestarzałych funkcji, dzięki czemu zestaw dostępnych funkcji będzie bardziej ortogonalny. Dlatego podoba mi się obsługa nowych makr, ponieważ zapewnia ona możliwość czyszczenia języka, oddzielając niektóre funkcje w niezależnych bibliotekach, które nie są częścią języka podstawowego.
Alexandru Nedelcu,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.