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


361

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

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


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

Ответы:


563

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 были введены манифесты . Но у них проблема в том, что они не могут представлять много полезных типов, таких как path-зависимые типы:

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

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

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из 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 также содержит руководство для Reflection .


19
Спасибо за Ваш ответ! Некоторые комментарии: 1) ==для типов представляет структурное равенство, а не ссылочное равенство. =:=принимать во внимание эквивалентности типов (даже неочевидные, такие как эквивалентности префиксов, которые поступают из разных зеркал), 2) оба TypeTagи AbsTypeTagоснованы на зеркалах. Разница в том, что TypeTagдопускаются только полностью инстанцированные типы (т.е. без каких-либо параметров типа или ссылок на абстрактные члены типа), 3) Подробное объяснение здесь: stackoverflow.com/questions/12093752
Евгений Бурмако,

10
4) Манифесты имеют проблему невозможности представить много полезных типов. По сути, они могут выражать только ссылки на типы (простые типы, такие как Intи универсальные типы, такие как List[Int]), оставляя в стороне такие типы Scala, как, например, уточнения, зависимые от пути типы, экзистенциалы, аннотированные типы. Кроме того, манифесты - это болт, поэтому они не могут использовать обширные знания, которые предоставляет компилятор, скажем, для вычисления линеаризации типа, выяснения, является ли один тип подтипами другого, и т. Д.
Евгений Бурмако,

9
5) Теги контрастного типа не «лучше интегрированы», они просто интегрированы с новым API отражения (в отличие от манифестов, которые ни с чем не интегрированы). Это обеспечивает доступ к тегам типов для определенных аспектов компилятора, например, для Types.scala(7kloc кода, который знает, как типы поддерживаются для совместной работы), Symbols.scala(3kloc кода, который знает, как работают таблицы символов) и т. Д.
Евгений Бурмако,

9
6) ClassTagявляется точной заменой ClassManifest, тогда TypeTagкак более или менее заменяет Manifest. Более или менее, потому что: 1) теги типа не несут стирания, 2) манифесты - большой взлом, и мы отказались от эмуляции его поведения с тегами типа. # 1 может быть исправлено с помощью границ контекста ClassTag и TypeTag, когда вам нужны как стирания, так и типы, и обычно не заботятся о # 2, потому что становится возможным отбросить все взломы и использовать полноценный API отражения вместо.
Евгений Бурмако

11
Я действительно надеюсь, что компилятор Scala в какой-то момент избавится от устаревших функций, чтобы сделать набор доступных функций более ортогональным. Вот почему мне нравится поддержка новых макросов, потому что она обеспечивает возможность очистки языка, отделяя некоторые функции в независимых библиотеках, которые не являются частью базового языка.
Александру Недельку
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.