Scala: TypeTag là gì và tôi sử dụng nó như thế nào?


361

Tất cả những gì tôi biết về TypeTags là bằng cách nào đó họ đã thay thế Biểu hiện. Thông tin trên Internet rất khan hiếm và không cung cấp cho tôi ý thức tốt về chủ đề này.

Vì vậy, tôi rất vui nếu ai đó chia sẻ liên kết đến một số tài liệu hữu ích trên TypeTags bao gồm các ví dụ và trường hợp sử dụng phổ biến. Câu trả lời chi tiết và giải thích cũng được chào đón.


1
Bài viết sau đây từ tài liệu Scala mô tả cả lý do và lý do của thẻ loại, cũng như cách sử dụng chúng trong mã của bạn: docs.scala-lang.org/overview/reflection/ tựa
btiernay

Câu trả lời:


563

Một cách TypeTaggiải quyết vấn đề mà các kiểu của Scala bị xóa trong thời gian chạy (kiểu xóa). Nếu chúng ta muốn làm

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

chúng tôi sẽ nhận được cảnh báo:

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

Để giải quyết vấn đề này Bản kê khai đã được giới thiệu cho Scala. Nhưng họ có vấn đề là không thể đại diện cho nhiều loại hữu ích, như loại phụ thuộc vào đường dẫn:

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

Do đó, chúng được thay thế bằng TypeTags , cả hai đều đơn giản hơn nhiều để sử dụng và được tích hợp tốt vào API Reflection mới. Với chúng, chúng ta có thể giải quyết vấn đề ở trên về các loại phụ thuộc đường dẫn một cách thanh lịch:

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

Chúng cũng dễ sử dụng để kiểm tra các tham số loại:

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

Tại thời điểm này, điều cực kỳ quan trọng là phải hiểu để sử dụng =:=(loại bằng) và <:<(quan hệ phụ) để kiểm tra đẳng thức. Không bao giờ sử dụng ==hoặc !=, trừ khi bạn hoàn toàn biết những gì bạn làm:

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

Cái sau kiểm tra sự bằng nhau về cấu trúc, thường không phải là điều nên làm vì nó không quan tâm đến những thứ như tiền tố (như trong ví dụ).

A TypeTaghoàn toàn do trình biên dịch tạo, điều đó có nghĩa là trình biên dịch tạo và điền vào TypeTagkhi người ta gọi một phương thức mong đợi như vậy TypeTag. Có ba dạng thẻ khác nhau:

ClassTagthay thế ClassManifesttrong khi TypeTagít nhiều là sự thay thế cho Manifest.

Cái trước cho phép làm việc hoàn toàn với các mảng chung:

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 chỉ cung cấp thông tin cần thiết để tạo các loại trong thời gian chạy (loại đã bị xóa):

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]

Như người ta có thể thấy ở trên, họ không quan tâm đến việc xóa loại, do đó, nếu muốn TypeTagsử dụng loại "đầy đủ" :

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

Như người ta có thể thấy, phương pháp tpecủa TypeTagkết quả trong một đầy đủ Type, đó là cùng chúng tôi nhận được khi typeOfđược gọi. Tất nhiên, có thể sử dụng cả hai ClassTagTypeTag:

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

Câu hỏi còn lại bây giờ là ý nghĩa của WeakTypeTagcái gì? Nói tóm lại, TypeTagđại diện cho một loại cụ thể (điều này có nghĩa là nó chỉ cho phép các loại được khởi tạo hoàn toàn) trong khi WeakTypeTagchỉ cho phép bất kỳ loại nào. Hầu hết thời gian người ta không quan tâm đó là cái gì (có nghĩa là TypeTagnên được sử dụng), nhưng ví dụ, khi macro được sử dụng sẽ hoạt động với các loại chung mà chúng cần:

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

Nếu thay máy WeakTypeTagvới TypeTagmột lỗi được ném:

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

Để được giải thích chi tiết hơn về sự khác biệt giữa TypeTagWeakTypeTagxem câu hỏi này: Scala Macros: Có thể tạo TypeTag từ loại T có các tham số loại chưa được giải quyết.

Trang web tài liệu chính thức của Scala cũng chứa một hướng dẫn về Reflection .


19
Cảm ơn câu trả lời của bạn! Một số ý kiến: 1) ==cho các loại đại diện cho bình đẳng cấu trúc, không phải là đẳng thức tham chiếu. =:=tính đến các loại tương đương (ngay cả những loại không rõ ràng như tương đương của các tiền tố đến từ các gương khác nhau), 2) Cả hai TypeTagAbsTypeTagđều dựa trên gương. Sự khác biệt là TypeTagchỉ cho phép các kiểu khởi tạo hoàn toàn (nghĩa là không có bất kỳ tham số loại hoặc tham chiếu loại thành viên trừu tượng nào), 3) Một lời giải thích chi tiết có ở đây: stackoverflow.com/questions/12093752
Eugene Burmako

10
4) Biểu hiện có vấn đề là không thể đại diện cho nhiều loại hữu ích. Về cơ bản, họ chỉ có thể biểu thị các loại ref (loại đơn giản như Intvà loại chung chung List[Int]), loại bỏ các loại Scala như ví dụ tinh chỉnh, loại phụ thuộc đường dẫn, loại tồn tại, loại chú thích. Các biểu hiện cũng là một tia sáng, vì vậy họ không thể sử dụng kiến ​​thức rộng lớn mà trình biên dịch đặt ra để tính toán tuyến tính hóa một loại, tìm hiểu xem một kiểu con loại khác, v.v.
Eugene Burmako

9
5) Đối với các loại thẻ tương phản không được "tích hợp tốt hơn", chúng được tích hợp đơn giản với API phản chiếu mới (không giống như các bảng kê khai không được tích hợp với bất cứ thứ gì). Điều này cung cấp quyền truy cập thẻ loại vào các khía cạnh nhất định của trình biên dịch, ví dụ: Types.scala(7kloc mã biết cách các loại được hỗ trợ để làm việc cùng nhau), Symbols.scala(3kloc mã biết cách các bảng biểu tượng hoạt động), v.v.
Eugene Burmako

9
6) ClassTaglà một sự thay thế chính xác cho ClassManifest, trong khi đó TypeTagít nhiều là một sự thay thế cho Manifest. Nhiều hơn hoặc ít hơn, bởi vì: 1) thẻ loại không mang theo xóa, 2) biểu hiện là một vụ hack lớn và chúng tôi đã từ bỏ mô phỏng hành vi của nó với thẻ loại. # 1 có thể được khắc phục bằng cách sử dụng cả giới hạn ngữ cảnh ClassTag và TypeTag khi bạn cần cả xóa và loại, và người ta thường không quan tâm đến # 2, bởi vì có thể loại bỏ tất cả các bản hack và sử dụng API phản chiếu chính thức thay thế.
Eugene Burmako

11
Tôi thực sự hy vọng rằng trình biên dịch Scala sẽ loại bỏ các tính năng không dùng nữa tại một số điểm, để làm cho tập hợp các tính năng có sẵn trở nên trực giao hơn. Đây là lý do tại sao tôi thích hỗ trợ macro mới vì nó cung cấp tiềm năng để dọn dẹp ngôn ngữ, tách một số tính năng trong các thư viện độc lập không phải là một phần của ngôn ngữ cơ sở.
Alexandru Nedelcu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.