Scala: Что такое TypeTag и как его использовать?

351

Все, что я знаю о TypeTags, это то, что они каким-то образом заменили Manifests. Информация в Интернете недостаточна и не дает мне хорошего представления о предмете.

Итак, я был бы рад, если бы кто-то поделился ссылкой на некоторые полезные материалы о TypeTags, включая примеры и популярные прецеденты. Подробные ответы и объяснения также приветствуются.

  • 0
    В следующей статье из документации по Scala описаны теги типа «что» и «почему», а также способы их использования в коде: docs.scala-lang.org/overviews/reflection/…
Теги:
types
scala-2.10
reification

1 ответ

478
Лучший ответ

A TypeTag решает проблему стирания типов Scala во время выполнения (стирание типа). Если мы хотим сделать

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

мы получим предупреждения:

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

Чтобы решить эту проблему, Манифесты были введены в Scala. Но у них есть проблема неспособность представить множество полезных типов, таких как типы, зависящие от пути:

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

Таким образом, они заменяются на TypeTags, которые намного проще использовать и хорошо интегрированы в новый API Reflection. С их помощью мы можем решить проблему выше относительно зависимых от пути типов:

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

Они также просты в использовании для проверки параметров типа:

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

В этот момент чрезвычайно важно понять, использовать =:= (тип равенства) и <:< (отношение подтипа) для проверок равенства. Никогда не используйте == или !=, если вы не знаете, что вы делаете:

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

Последний проверяет структурное равенство, которое часто не является тем, что должно быть сделано, потому что ему не нужны такие вещи, как префиксы (как в примере).

A TypeTag полностью генерируется компилятором, это означает, что компилятор создает и заполняет TypeTag, когда вы вызываете метод, ожидающий такого TypeTag. Существуют три различные формы тегов:

ClassTag заменяет ClassManifest, тогда как TypeTag является более или менее заменой для Manifest.

Первая позволяет полностью работать с общими массивами:

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 предоставляет только информацию, необходимую для создания типов во время выполнения (которые стираются):

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]

Как видно выше, они не заботятся о стирании стилей, поэтому, если вам нужны "полные" типы TypeTag, следует использовать:

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

Как видно, метод tpe of TypeTag приводит к полному Type, который мы получаем при вызове typeOf. Конечно, можно использовать оба параметра: ClassTag и 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]])

Остается вопрос, в чем смысл WeakTypeTag? Короче говоря, TypeTag представляет конкретный тип (это означает, что он допускает только полностью создаваемые типы), тогда как WeakTypeTag просто разрешает любой тип. Большую часть времени вам все равно, что (что означает TypeTag), но, например, когда используются макросы, которые должны работать с типичными типами, они необходимы:

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

Если заменить WeakTypeTag на TypeTag, возникает ошибка:

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

Более подробное объяснение различий между TypeTag и WeakTypeTag см. в этом вопросе: Scala Макросы: "невозможно создать TypeTag из типа T, имеющего нерешенные параметры типа"

Официальный сайт документации Scala также содержит руководство для отражения.

  • 18
    Спасибо за Ваш ответ! Некоторые комментарии: 1) == для типов представляет структурное равенство, а не ссылочное равенство. =:= принимать во внимание эквивалентность типов (даже неочевидные, такие как эквивалентности префиксов, поступающих из разных зеркал), 2) Оба типа TypeTag и AbsTypeTag основаны на зеркалах. Разница в том, что TypeTag допускает только полностью инстанцированные типы (т.е. без каких-либо параметров типа или ссылок на абстрактные члены типа), 3) Подробное объяснение здесь: stackoverflow.com/questions/12093752
  • 10
    4) Манифесты имеют проблему невозможности представить много полезных типов. По сути, они могут выражать только ссылки на типы (простые типы, такие как Int и универсальные типы, такие как List[Int] ), оставляя в стороне такие типы Scala, как, например, уточнения, зависимые от пути типы, экзистенциалы, аннотированные типы. Кроме того, манифесты - это болт, поэтому они не могут использовать обширные знания, которыми обладает компилятор, скажем, для вычисления линеаризации типа, выяснения, является ли один тип подтипами другого, и т. Д.
Показать ещё 7 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню