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

Wszystko co wiem o Typetagach to to, że w jakiś sposób zastąpiły manifesty. Informacji w Internecie jest niewiele i nie daje mi dobrego wyczucia tematu.

Więc byłbym szczęśliwy, gdyby ktoś udostępnił link do niektórych przydatnych materiałów na TypeTags, w tym przykładów i popularnych przypadków użycia. Mile widziane są również szczegółowe odpowiedzi i wyjaśnienia.

Author: Sergey Weiss, 2012-08-31

1 answers

A TypeTag rozwiązuje problem, że typy Scali są kasowane w czasie wykonywania (type erasure). If we wanna do

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 problemmanifesty zostały wprowadzone do Scali. Ale mają problem z tym, że nie są w stanie 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] = [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

W ten sposób są one zastępowane przez TypeTags , które są znacznie prostsze w użyciu i dobrze zintegrowane z nowym API Reflection. Dzięki nim możemy rozwiązać powyższy problem o Path-dependent-types elegancko: {]}

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 do sprawdzania parametrów 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, aby zrozumieć, aby używać =:= (równość typu) i <:< (relacja podtypu) do sprawdzania 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ą, która często nie jest tym, co należy zrobić, ponieważ nie dbać o rzeczy takie jak prefiksy(jak w przykładzie).

A TypeTag jest całkowicie wygenerowany przez kompilator, co oznacza, że kompilator tworzy i wypełnia TypeTag gdy wywołana zostanie metoda oczekująca takiego TypeTag. Istnieją trzy różne formy tagów:

ClassTag substytuty ClassManifest {[12] } jest mniej więcej zamiennikiem Manifest.

Pierwszy pozwala w pełni pracować z tablicami generycznymi:

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ą kasowane typu):

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 dbają o usuwanie typów, dlatego jeśli ktoś chce "pełne" typy TypeTag powinny być używane: {]}

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 tpe z TypeTag daje pełne Type, czyli to samo otrzymujemy, gdy typeOf jest dzwoniłem. Oczywiście możliwe jest użycie zarówno ClassTag, jak i 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, jaki jest sens WeakTypeTag? W skrócie, TypeTag reprezentuje konkretny typ (oznacza to, że pozwala tylko na pełne instancje typów), podczas gdy WeakTypeTag pozwala tylko na dowolny typ. Większość czasu nie obchodzi, co jest co (co oznacza, że TypeTag powinny być używane), ale na przykład, gdy używane są makra, które powinny działać z typami generycznymi, są 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 ktoś zastąpi WeakTypeTag Z TypeTag wyrzucany 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 TypeTag i WeakTypeTagzobacz to pytanie: makra Scali: "nie można utworzyć znacznika typu Z Typu T o nierozwiązanych parametrach typu"

Scala jest jedną z najbardziej rozpoznawalnych i najbardziej rozpoznawalnych firm na świecie.
 510
Author: kiritsuku,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:38