Scala: 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> 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หมายถึงประเภทคอนกรีต (ซึ่งหมายความว่าจะช่วยให้ประเภท instantiated อย่างเต็มที่เท่านั้น) ในขณะที่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ดูคำถามนี้: มาโครสกาล่า:“ ไม่สามารถสร้าง TypeTag จากประเภท T ที่มีพารามิเตอร์ประเภทที่ไม่ได้รับการแก้ไข”

เว็บไซต์อย่างเป็นทางการของเอกสาร Scala นอกจากนี้ยังมีคำแนะนำสำหรับการสะท้อน


19
ขอบคุณสำหรับคำตอบ! ความคิดเห็นบางส่วน: 1) ==สำหรับประเภทแสดงถึงความเท่าเทียมกันของโครงสร้างไม่ใช่ความเท่าเทียมกันในการอ้างอิง =:=คำนึงถึงความเสมอภาคของประเภทบัญชี (แม้จะไม่ชัดเจนเช่นการเทียบเท่าของคำนำหน้าที่มาจากมิรเรอร์ที่แตกต่างกัน), 2) ทั้งคู่TypeTagและAbsTypeTagขึ้นอยู่กับมิเรอร์ ความแตกต่างคือTypeTagอนุญาตเฉพาะประเภทอินสแตนซ์ที่สมบูรณ์เท่านั้น (เช่นไม่มีพารามิเตอร์ประเภทใด ๆ หรือการอ้างอิงสมาชิกประเภทนามธรรม), 3) คำอธิบายโดยละเอียดอยู่ที่นี่: stackoverflow.com/questions/12093752
Eugene Burmako

10
4) การแสดงออกมีปัญหาที่ไม่สามารถเป็นตัวแทนของประเภทที่มีประโยชน์มากมาย โดยพื้นฐานแล้วพวกเขาสามารถแสดงการอ้างอิงประเภท (ประเภทธรรมดาเช่นIntและประเภททั่วไปเช่นList[Int]) โดยไม่ต้องระบุประเภทสกาล่าเช่นการปรับแต่งประเภทขึ้นอยู่กับเส้นทางประเภทที่มีอยู่ นอกจากนี้การแสดงออกยังเป็นสิ่งที่ทำให้พวกเขาไม่สามารถใช้ความรู้มากมายที่คอมไพเลอร์ posesses พูดคำนวณเป็นเส้นตรงของประเภทค้นหาว่าประเภทย่อยอีกประเภทหนึ่งหรือไม่
Eugene Burmako

9
5) สำหรับแท็กประเภทความคมชัดไม่ใช่ "รวมที่ดีขึ้น" พวกเขาจะถูกรวมเข้ากับ API การสะท้อนใหม่ (ซึ่งแตกต่างจากรายการที่ไม่ได้รวมกับสิ่งใด) สิ่งนี้จะช่วยให้แท็กประเภทสามารถเข้าถึงบางแง่มุมของคอมไพเลอร์เช่นถึงTypes.scala(7kloc ของรหัสที่รู้ว่าประเภทสนับสนุนการทำงานร่วมกันได้อย่างไร) Symbols.scala(3kloc ของรหัสที่รู้ว่าตารางสัญลักษณ์ทำงานอย่างไร)
Eugene Burmako

9
6) ClassTagเป็นดรอปแทนสำหรับที่แน่นอนClassManifestในขณะที่จะมากหรือน้อยทดแทนTypeTag Manifestมากขึ้นหรือน้อยลงเนื่องจาก: 1) แท็กประเภทไม่ได้ลบข้อมูล 2) รายการจะมีการแฮ็กขนาดใหญ่และเราก็เลิกเลียนแบบพฤติกรรมด้วยแท็กประเภท # 1 สามารถแก้ไขได้โดยใช้ทั้งบริบท ClassTag และ TypeTag เมื่อคุณต้องการทั้งการลบและประเภทและมักจะไม่สนใจ # 2 เนื่องจากเป็นไปได้ที่จะทิ้งแฮ็กทั้งหมดและใช้ API การสะท้อนเต็ม แทน.
Eugene Burmako

11
ฉันหวังว่าคอมไพเลอร์ Scala จะกำจัดฟีเจอร์ที่เลิกใช้ไปแล้วในบางจุดเพื่อทำให้ชุดฟีเจอร์ที่มีอยู่นั้นมีมุมฉากมากขึ้น นี่คือเหตุผลที่ฉันชอบมาโครตัวใหม่ที่รองรับเพราะมันมีศักยภาพในการทำความสะอาดภาษาโดยแยกคุณสมบัติบางอย่างในไลบรารีอิสระที่ไม่ได้เป็นส่วนหนึ่งของภาษาพื้นฐาน
Alexandru Nedelcu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.