Scala: Apa itu TypeTag dan bagaimana cara menggunakannya?

361

Yang saya tahu tentang TypeTag adalah mereka entah bagaimana menggantikan Manifest. Informasi di Internet langka dan tidak memberi saya pengertian yang baik tentang subjek.

Jadi saya akan senang jika seseorang membagikan tautan ke beberapa materi berguna di TypeTag termasuk contoh dan kasus penggunaan populer. Jawaban dan penjelasan terperinci juga diterima.

Sergey Weiss
sumber
1
Artikel berikut dari dokumentasi Scala menjelaskan apa dan mengapa tag jenis, serta bagaimana menggunakannya dalam kode Anda: docs.scala-lang.org/overviews/reflection/…
btiernay

Jawaban:

563

A TypeTagmemecahkan masalah bahwa tipe Scala dihapus pada saat runtime (type erasure). Jika kita ingin melakukannya

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

kami akan mendapat peringatan:

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

Untuk mengatasi masalah ini, Manifes diperkenalkan ke Scala. Tetapi mereka memiliki masalah karena tidak dapat mewakili banyak tipe yang berguna, seperti tipe path-dependen:

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

Dengan demikian, mereka digantikan oleh TypeTags , yang keduanya lebih mudah digunakan dan terintegrasi dengan baik ke dalam API Refleksi baru. Dengan mereka, kita dapat memecahkan masalah di atas tentang tipe path-dependent secara elegan:

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

Mereka juga mudah digunakan untuk memeriksa parameter tipe:

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

Pada titik ini, sangat penting untuk memahami penggunaan =:=(jenis kesetaraan) dan <:<(hubungan subtipe) untuk pemeriksaan kesetaraan. Jangan pernah menggunakan ==atau !=, kecuali jika Anda benar-benar tahu apa yang Anda lakukan:

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

Yang terakhir memeriksa kesetaraan struktural, yang sering kali bukan apa yang harus dilakukan karena tidak peduli tentang hal-hal seperti awalan (seperti dalam contoh).

A TypeTagsepenuhnya dihasilkan oleh kompiler, artinya kompiler membuat dan mengisi TypeTagketika seseorang memanggil metode yang mengharapkan a TypeTag. Ada tiga bentuk tag yang berbeda:

ClassTagpengganti ClassManifestsedangkan TypeTagkurang lebih pengganti Manifest.

Yang pertama memungkinkan untuk sepenuhnya bekerja dengan array generik:

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 hanya menyediakan informasi yang diperlukan untuk membuat tipe saat runtime (yang merupakan tipe terhapus):

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]

Seperti yang dapat dilihat di atas, mereka tidak peduli tentang penghapusan tipe, oleh karena itu jika seseorang ingin tipe "penuh" TypeTagharus digunakan:

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

Seperti yang dapat dilihat, metode tpedari TypeTaghasil dalam penuh Type, yang sama kita dapatkan ketika typeOfdisebut. Tentu saja, dimungkinkan untuk menggunakan keduanya, ClassTagdan 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]])

Pertanyaan yang tersisa sekarang adalah apa artinya WeakTypeTag? Singkatnya, TypeTagmerupakan tipe beton (ini berarti hanya memungkinkan tipe instantiated sepenuhnya) sedangkan WeakTypeTaghanya memungkinkan jenis apa pun. Sebagian besar waktu seseorang tidak peduli yang mana (yang berarti TypeTagharus digunakan), tetapi misalnya, ketika makro digunakan yang harus bekerja dengan jenis generik mereka diperlukan:

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

Jika seseorang mengganti WeakTypeTagdengan TypeTagkesalahan dilemparkan:

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

Untuk penjelasan yang lebih terperinci tentang perbedaan antara TypeTagdan WeakTypeTagmelihat pertanyaan ini: Scala Macros: "tidak dapat membuat TypeTag dari tipe T yang memiliki parameter tipe yang belum terselesaikan"

Situs dokumentasi resmi Scala juga berisi panduan untuk Refleksi .

kiritsuku
sumber
19
Terima kasih atas jawaban anda! Beberapa komentar: 1) ==untuk tipe mewakili kesetaraan struktural, bukan referensi kesetaraan. =:=memperhitungkan kesetaraan jenis akun (bahkan yang tidak jelas seperti kesetaraan awalan yang berasal dari mirror yang berbeda), 2) Keduanya TypeTagdan AbsTypeTagdidasarkan pada mirror. Perbedaannya adalah bahwa TypeTaghanya memungkinkan tipe instantiated sepenuhnya (yaitu tanpa parameter tipe atau referensi anggota tipe abstrak), 3) Penjelasan terperinci di sini: stackoverflow.com/questions/12093752
Eugene Burmako
10
4) Manifes memiliki masalah karena tidak dapat mewakili banyak tipe yang bermanfaat. Pada dasarnya mereka hanya dapat mengekspresikan ref tipe (tipe polos seperti Intdan tipe generik seperti List[Int]), meninggalkan tipe Scala seperti misalnya penyempitan, tipe path-dependent, eksistensial, tipe beranotasi. Manifes juga merupakan baut, sehingga mereka tidak dapat menggunakan pengetahuan luas yang dimiliki oleh kompiler untuk, katakanlah, menghitung linierisasi suatu jenis, mencari tahu apakah satu jenis subtipe lainnya, dll.
Eugene Burmako
9
5) Untuk tag jenis kontras tidak "lebih terintegrasi", mereka hanya diintegrasikan dengan API refleksi baru (tidak seperti manifes yang tidak terintegrasi dengan apa pun). Ini memberikan tag jenis akses ke aspek-aspek tertentu dari kompiler, misalnya untuk Types.scala(7 kloc kode yang tahu bagaimana jenis didukung untuk bekerja bersama), Symbols.scala(3 kloc kode yang tahu bagaimana tabel simbol bekerja), dll.
Eugene Burmako
9
6) ClassTagadalah pengganti drop-in yang tepat untuk ClassManifest, sedangkan TypeTaglebih atau kurang merupakan pengganti Manifest. Lebih atau kurang, karena: 1) tag jenis tidak membawa penghapusan, 2) manifes adalah hack besar, dan kami menyerah meniru perilakunya dengan tag ketik. # 1 dapat diperbaiki dengan menggunakan kedua konteks ClassTag dan TypeTag batas ketika Anda membutuhkan penghapusan dan jenis, dan satu biasanya tidak peduli tentang # 2, karena menjadi mungkin untuk membuang semua peretasan dan menggunakan API refleksi penuh sebagai gantinya.
Eugene Burmako
11
Saya benar-benar berharap bahwa kompiler Scala akan menghilangkan fitur-fitur yang sudah usang di beberapa titik, untuk membuat set fitur yang tersedia menjadi lebih ortogonal. Inilah sebabnya saya menyukai dukungan makro baru karena menyediakan potensi untuk membersihkan bahasa, memisahkan beberapa fitur di perpustakaan independen yang bukan bagian dari bahasa dasar.
Alexandru Nedelcu