Скала: Що таке TypeTag і як ним користуватися?


361

Все, що я знаю про TypeTags - це те, що вони якось замінили Manifests. Інформація в Інтернеті мізерна і не дає мені добре розуміти цю тему.

Тож я б радий, якби хтось поділився посиланням на корисні матеріали на TypeTags, включаючи приклади та популярні випадки використання. Детальні відповіді та пояснення також вітаються.


1
Наступна стаття з документації Scala описує як те, що і навіщо типу тегів, а також як їх використовувати у своєму коді: docs.scala-lang.org/overviews/reflection/…
btiernay

Відповіді:


563

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] = 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 , які одночасно набагато простіші у використанні та добре інтегровані в новий 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генерується повністю компілятором, це означає, що компілятор створює і заповнює a, 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 Macros: "не можна створити TypeTag з типу T, що має невирішені параметри типу"

Офіційний сайт документації Scala також містить посібник з рефлексії .


19
Дякую за вашу відповідь! Деякі зауваження: 1) ==для типів означає структурну рівність, а не референтну рівність. =:=враховують типові еквіваленти (навіть не очевидні, такі як еквівалентність префіксів, що надходять від різних дзеркал), 2) Обидва TypeTagі AbsTypeTagбазуються на дзеркалах. Різниця полягає в тому, що TypeTagтільки дозволяє повністю втілені типи (тобто без будь - яких параметрів типу або посилання на абстрактні члени типу), 3) докладне пояснення знаходиться тут: stackoverflow.com/questions/12093752
Євген Burmako

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.