ฉันจะลบประเภทใน Scala ได้อย่างไร หรือเหตุใดฉันจึงไม่สามารถรับพารามิเตอร์ type ของคอลเล็กชันของฉันได้


370

มันเป็นความจริงที่น่าเศร้าของชีวิตใน Scala ที่ว่าถ้าคุณยกตัวอย่าง List [Int] คุณสามารถตรวจสอบว่าอินสแตนซ์ของคุณเป็น List และคุณสามารถตรวจสอบได้ว่าองค์ประกอบใด ๆ ของมันเป็น Int แต่ไม่ใช่ว่าเป็น List Int] ตามที่สามารถตรวจสอบได้ง่าย:

scala> List(1,2,3) match {
     | case l : List[String] => println("A list of strings?!")
     | case _ => println("Ok")
     | }
warning: there were unchecked warnings; re-run with -unchecked for details
A list of strings?!

ตัวเลือก -Checked ทำให้ตำหนิอย่างเต็มที่ในการลบประเภท:

scala>  List(1,2,3) match {
     |  case l : List[String] => println("A list of strings?!")
     |  case _ => println("Ok")
     |  }
<console>:6: warning: non variable type-argument String in type pattern is unchecked since it is eliminated by erasure
        case l : List[String] => println("A list of strings?!")
                 ^
A list of strings?!

ทำไมเป็นอย่างนั้นและฉันจะไปรอบ ๆ มันได้อย่างไร


Scala 2.8 Beta 1 RC4 เพิ่งทำการเปลี่ยนแปลงบางอย่างเกี่ยวกับการลบประเภทงาน ฉันไม่แน่ใจว่าสิ่งนี้มีผลกระทบต่อคำถามของคุณโดยตรงหรือไม่
Scott Morrison

1
นั่นเป็นเพียงสิ่งที่ประเภทการลบออกไปว่ามีการเปลี่ยนแปลง ส่วนสั้น ๆ ของมันสามารถสรุปได้ว่า " ข้อเสนอ: การลบ" Object with A "คือ" A "แทนที่จะเป็น" Object " " ข้อมูลจำเพาะที่แท้จริงค่อนข้างซับซ้อนกว่า มันเกี่ยวกับมิกซ์อินในอัตราใด ๆ และคำถามนี้เป็นคำถามเกี่ยวกับยาชื่อสามัญ
Daniel C. Sobral

ขอบคุณสำหรับการชี้แจง - ฉันเป็นผู้มาใหม่ scala ฉันรู้สึกเหมือนตอนนี้เป็นเวลาที่ไม่ดีที่จะกระโดดเข้าสู่สกาล่า ก่อนหน้านี้ฉันสามารถเรียนรู้การเปลี่ยนแปลงใน 2.8 จากฐานที่ดีหลังจากนั้นฉันไม่จำเป็นต้องรู้ถึงความแตกต่าง!
Scott Morrison

1
นี่เป็นคำถามที่ค่อนข้างที่เกี่ยวข้องเกี่ยวกับsTypeTag
pvorb

2
วิ่งscala 2.10.2ฉันเห็นคำเตือนนี้แทน: <console>:9: warning: fruitless type test: a value of type List[Int] cannot also be a List[String] (but still might match its erasure) case list: List[String] => println("a list of strings?") ^ฉันพบว่าคำถามและคำตอบของคุณมีประโยชน์มาก แต่ฉันไม่แน่ใจว่าคำเตือนที่อัปเดตนี้มีประโยชน์ต่อผู้อ่านหรือไม่
เควินเมเรดิ ธ

คำตอบ:


243

คำตอบนี้ใช้Manifest-API ซึ่งคัดค้าน ณ Scala 2.10 โปรดดูคำตอบด้านล่างสำหรับวิธีแก้ปัญหาเพิ่มเติม

Scala ถูกกำหนดด้วย Type Erasure เนื่องจาก Java Virtual Machine (JVM) ซึ่งแตกต่างจาก Java ไม่ได้รับข้อมูลทั่วไป ซึ่งหมายความว่า ณ เวลารันไทม์มีคลาสเท่านั้นไม่ใช่พารามิเตอร์ชนิด ในตัวอย่าง JVM รู้ว่ามันมีการจัดการแต่ไม่ว่ารายการนี้จะถูกแปรด้วยscala.collection.immutable.ListInt

โชคดีที่มีคุณลักษณะในสกาล่าที่ช่วยให้คุณสามารถแก้ไขได้ มันเป็นManifest Manifest เป็นคลาสที่อินสแตนซ์เป็นวัตถุที่เป็นตัวแทนประเภท เนื่องจากอินสแตนซ์เหล่านี้เป็นวัตถุคุณสามารถส่งต่อไปเก็บและโดยทั่วไปเรียกวิธีการเหล่านั้น ด้วยการสนับสนุนพารามิเตอร์โดยนัยมันจะกลายเป็นเครื่องมือที่ทรงพลังมาก ยกตัวอย่างเช่น

object Registry {
  import scala.reflect.Manifest

  private var map= Map.empty[Any,(Manifest[_], Any)] 

  def register[T](name: Any, item: T)(implicit m: Manifest[T]) {
    map = map.updated(name, m -> item)
  }

  def get[T](key:Any)(implicit m : Manifest[T]): Option[T] = {
    map get key flatMap {
      case (om, s) => if (om <:< m) Some(s.asInstanceOf[T]) else None
    }     
  }
}

scala> Registry.register("a", List(1,2,3))

scala> Registry.get[List[Int]]("a")
res6: Option[List[Int]] = Some(List(1, 2, 3))

scala> Registry.get[List[String]]("a")
res7: Option[List[String]] = None

เมื่อจัดเก็บองค์ประกอบเราจะจัดเก็บ "รายการ" ด้วย Manifest เป็นคลาสที่อินสแตนซ์แสดงประเภท Scala วัตถุเหล่านี้มีข้อมูลมากกว่าที่ JVM ทำซึ่งทำให้เราสามารถทดสอบชนิดที่กำหนดพารามิเตอร์แบบเต็ม

อย่างไรก็ตามโปรดทราบว่า a Manifestยังคงเป็นคุณสมบัติที่พัฒนาขึ้น เป็นตัวอย่างของข้อ จำกัด ปัจจุบันมันไม่รู้อะไรเลยเกี่ยวกับความแปรปรวนและสมมติว่าทุกอย่างเป็นตัวแปรร่วม ฉันคาดหวังว่ามันจะมีเสถียรภาพมากขึ้นและมั่นคงเมื่อห้องสมุดสะท้อน Scala ปัจจุบันภายใต้การพัฒนาเสร็จแล้ว


3
วิธีการสามารถกำหนดเป็นget for ((om, v) <- _map get key if om <:< m) yield v.asInstanceOf[T]
Aaron Novstrup

4
@Aaron คำแนะนำที่ดีมาก แต่ฉันกลัวว่ามันอาจปิดบังรหัสสำหรับคนที่ค่อนข้างใหม่กับ Scala ฉันไม่ได้มีประสบการณ์กับสกาล่ามากนักเมื่อฉันเขียนโค้ดนั้นซึ่งบางครั้งก่อนที่ฉันจะใส่ไว้ในคำถาม / คำตอบนี้
Daniel C. Sobral

6
@KimStebel คุณรู้ไหมว่าTypeTagจริง ๆ แล้วจะใช้ในการจับคู่รูปแบบโดยอัตโนมัติ เจ๋งใช่มั้ย
Daniel C. Sobral

1
เย็น! บางทีคุณควรเพิ่มคำตอบลงไป
Kim Stebel

1
หากต้องการตอบคำถามของฉันเองด้านบน: ใช่คอมไพเลอร์สร้างManifestพารามิเตอร์เองดู: stackoverflow.com/a/11495793/694469 "อินสแตนซ์ [manifest / type-tag] [... ] กำลังถูกสร้างขึ้นโดยคอมไพเลอร์ "
KajMagnus

96

คุณสามารถทำได้โดยใช้ TypeTags (ตามที่ Daniel พูดถึงแล้ว แต่ฉันจะอธิบายให้ชัดเจน):

import scala.reflect.runtime.universe._
def matchList[A: TypeTag](list: List[A]) = list match {
  case strlist: List[String @unchecked] if typeOf[A] =:= typeOf[String] => println("A list of strings!")
  case intlist: List[Int @unchecked] if typeOf[A] =:= typeOf[Int] => println("A list of ints!")
}

คุณสามารถทำได้โดยใช้ ClassTags (ซึ่งช่วยให้คุณไม่ต้องพึ่งสกาล่าสะท้อน):

import scala.reflect.{ClassTag, classTag}
def matchList2[A : ClassTag](list: List[A]) = list match {
  case strlist: List[String @unchecked] if classTag[A] == classTag[String] => println("A List of strings!")
  case intlist: List[Int @unchecked] if classTag[A] == classTag[Int] => println("A list of ints!")
}

ClassTags สามารถใช้ได้ตราบใดที่คุณไม่คาดหวังว่าพารามิเตอร์ของAตัวเองจะเป็นประเภททั่วไป

น่าเสียดายที่มันเป็น verbose เล็ก ๆ น้อย ๆ และคุณจำเป็นต้องมีคำอธิบายประกอบ @unchecked เพื่อยับยั้งการเตือนคอมไพเลอร์ TypeTag อาจรวมอยู่ในการจับคู่รูปแบบโดยอัตโนมัติโดยคอมไพเลอร์ในอนาคต: https://issues.scala-lang.org/browse/SI-6517


2
สิ่งที่เกี่ยวกับการลบที่ไม่จำเป็นออกไป[List String @unchecked]เพราะมันไม่ได้เพิ่มอะไรให้กับรูปแบบนี้ (เพียงแค่ใช้case strlist if typeOf[A] =:= typeOf[String] =>จะทำหรือแม้ว่าcase _ if typeOf[A] =:= typeOf[String] =>ตัวแปรที่ถูกผูกไว้ไม่จำเป็นในเนื้อหาของcase)
Nader Ghanbari

1
ฉันเดาว่ามันจะใช้ได้กับตัวอย่างที่กำหนด แต่ฉันคิดว่าประเพณีที่แท้จริงส่วนใหญ่จะได้รับประโยชน์จากการมีประเภทขององค์ประกอบ
tksfz

ในตัวอย่างด้านบนชิ้นส่วนที่ไม่ได้ตรวจสอบด้านหน้าของเงื่อนไขการ์ดจะทำการร่ายหรือไม่? คุณจะไม่ได้รับการยกเว้นในชั้นเรียนเมื่อต้องผ่านการแข่งขันในวัตถุแรกที่ไม่สามารถส่งไปยังสตริงได้หรือไม่?
Toby

อืมข้าเชื่อว่าจะไม่มีนักแสดงมาก่อนที่จะใช้ยาม - บิตที่ไม่ได้ตรวจสอบนั้นเป็นแบบไม่ใช้จนกว่ารหัสทางด้านขวา=>จะถูกดำเนินการ (และเมื่อรันโค้ดบน rhs เจ้าหน้าที่จะให้การรับประกันแบบคงที่กับประเภทขององค์ประกอบอาจมีการส่งที่นั่น แต่จะปลอดภัย)
tksfz

วิธีแก้ปัญหานี้ทำให้เกิดโอเวอร์เฮดของรันไทม์หรือไม่
stanislav.chetvertkov

65

คุณสามารถใช้Typeableประเภทคลาสจากไม่มีรูปแบบเพื่อให้ได้ผลลัพธ์ตามที่ต้องการ

ตัวอย่างเซสชัน REPL

scala> import shapeless.syntax.typeable._
import shapeless.syntax.typeable._

scala> val l1 : Any = List(1,2,3)
l1: Any = List(1, 2, 3)

scala> l1.cast[List[String]]
res0: Option[List[String]] = None

scala> l1.cast[List[Int]]
res1: Option[List[Int]] = Some(List(1, 2, 3))

การcastดำเนินการจะถูกลบอย่างแม่นยำมากที่สุดเท่าที่เป็นไปได้เนื่องจากTypeableอินสแตนซ์ในขอบเขตพร้อมใช้งาน


14
ควรสังเกตว่าการดำเนินการ "cast" นั้นจะดำเนินการซ้ำ ๆ ตลอดทั้งคอลเลกชันและคอลเล็กชั่นย่อยและตรวจสอบว่าค่าทั้งหมดที่เกี่ยวข้องนั้นเป็นประเภทที่ถูกต้องหรือไม่ (กล่าวคือl1.cast[List[String]]ทำโดยประมาณfor (x<-l1) assert(x.isInstanceOf[String]) สำหรับโครงสร้างข้อมูลขนาดใหญ่หรือหากมีการปลดเปลื้องเกิดขึ้นบ่อยครั้งอาจเป็นโอเวอร์เฮดที่ยอมรับไม่ได้
Dominique Unruh

16

ฉันคิดวิธีแก้ปัญหาง่ายๆที่พอเพียงในสถานการณ์ที่ จำกัด การใช้งานการห่อประเภทพารามิเตอร์ที่จะประสบปัญหาการลบประเภทในคลาส wrapper ที่สามารถใช้ในการจับคู่คำสั่ง

case class StringListHolder(list:List[String])

StringListHolder(List("str1","str2")) match {
    case holder: StringListHolder => holder.list foreach println
}

สิ่งนี้มีเอาต์พุตที่คาดไว้และ จำกัด เนื้อหาของคลาสเคสของเราเป็นชนิดรายการที่ต้องการ, รายการสตริง

รายละเอียดเพิ่มเติมได้ที่นี่: http://www.scalafied.com/?p=60


14

มีวิธีที่จะเอาชนะปัญหาการลบประเภทใน Scala ในการเอาชนะการลบประเภทในการจับคู่ 1และการลบประเภทการเอาชนะในการจับคู่ 2 (ความแปรปรวน)เป็นคำอธิบายของวิธีการเขียนโค้ดผู้ช่วยบางคนเพื่อห่อประเภทรวมถึงความแปรปรวนสำหรับการจับคู่


สิ่งนี้ไม่เอาชนะการลบประเภท ในตัวอย่างของเขาให้ทำ val x: Any = List (1,2,3); x match {case IntList (l) => println (s "จับคู่ $ {l (1)}"); กรณี _ => println (s "ไม่ตรงกัน")} สร้าง "ไม่ตรงกัน"
user48956

คุณสามารถดู scala 2.10 macros ได้
อเล็กซ์

11

ฉันพบวิธีแก้ปัญหาที่ดีกว่าเล็กน้อยสำหรับข้อ จำกัด ของภาษาที่ยอดเยี่ยมนี้

ใน Scala ปัญหาการลบประเภทจะไม่เกิดขึ้นกับอาร์เรย์ ฉันคิดว่ามันง่ายกว่าที่จะสาธิตสิ่งนี้ด้วยตัวอย่าง

ให้เราบอกว่าเรามีรายการ(Int, String)แล้วคำเตือนการลบประเภทต่อไปนี้

x match {
  case l:List[(Int, String)] => 
  ...
}

หากต้องการแก้ไขปัญหานี้ก่อนอื่นให้สร้างคลาสเคส:

case class IntString(i:Int, s:String)

จากนั้นในการจับคู่รูปแบบทำสิ่งที่ชอบ:

x match {
  case a:Array[IntString] => 
  ...
}

ซึ่งดูเหมือนว่าจะทำงานได้อย่างสมบูรณ์แบบ

สิ่งนี้จะต้องมีการเปลี่ยนแปลงเล็กน้อยในรหัสของคุณเพื่อทำงานกับอาร์เรย์แทนรายการ แต่ไม่ควรเป็นปัญหาสำคัญ

โปรดทราบว่าการใช้case a:Array[(Int, String)]จะยังคงให้คำเตือนการลบประเภทดังนั้นจึงจำเป็นต้องใช้คลาสคอนเทนเนอร์ใหม่ (ในตัวอย่างนี้IntString)


10
"ข้อ จำกัด ของภาษาที่ยอดเยี่ยมเป็นอย่างอื่น" มันเป็นข้อ จำกัด ของสกาล่าและข้อ จำกัด ของ JVM น้อยกว่า บางทีสกาล่าอาจได้รับการออกแบบให้รวมข้อมูลประเภทตามที่วิ่งบน JVM แต่ฉันไม่คิดว่าการออกแบบแบบนี้จะรักษาความสามารถในการทำงานร่วมกันกับ Java ไว้ (เช่นตามที่ออกแบบไว้คุณสามารถเรียก Scala จาก Java)
Carl G

1
ในฐานะที่เป็นผู้ติดตามการสนับสนุน reified generics สำหรับ Scala ใน. NET / CLRเป็นไปได้อย่างต่อเนื่อง
Carl G

6

ตั้งแต่ Java List[_]ไม่ทราบชนิดองค์ประกอบที่เกิดขึ้นจริงผมพบว่ามันมีประโยชน์มากที่สุดกับการใช้เพียง จากนั้นคำเตือนจะหายไปและรหัสอธิบายความจริง - มันเป็นรายการของสิ่งที่ไม่รู้จัก


4

ฉันสงสัยว่านี่เป็นวิธีแก้ปัญหาที่เหมาะสมหรือไม่:

scala> List(1,2,3) match {
     |    case List(_: String, _*) => println("A list of strings?!")
     |    case _ => println("Ok")
     | }

ไม่ตรงกับกรณี "รายการที่ว่างเปล่า" แต่ให้ข้อผิดพลาดในการคอมไพล์ไม่ใช่คำเตือน!

error: type mismatch;
found:     String
requirerd: Int

ในทางกลับกันดูเหมือนว่าจะทำงาน ....

scala> List(1,2,3) match {
     |    case List(_: Int, _*) => println("A list of ints")
     |    case _ => println("Ok")
     | }

มันดีกว่าหรือว่าฉันพลาดประเด็นตรงนี้ไป?


3
ไม่ทำงานกับรายการ (1, "a", "b") ซึ่งมีรายการประเภท [ใด ๆ ]
sullivan-

1
แม้ว่าประเด็นของซัลลิแวนนั้นถูกต้องและมีปัญหาเกี่ยวกับการสืบทอดฉันยังพบว่ามีประโยชน์
เซท


0

ฉันต้องการเพิ่มคำตอบที่ generalises ปัญหา: ได้รับการเป็นตัวแทน String ประเภทของรายการของฉันที่ runtime

import scala.reflect.runtime.universe._

def whatListAmI[A : TypeTag](list : List[A]) = {
    if (typeTag[A] == typeTag[java.lang.String]) // note that typeTag[String] does not match due to type alias being a different type
        println("its a String")
    else if (typeTag[A] == typeTag[Int])
        println("its a Int")

    s"A List of ${typeTag[A].tpe.toString}"
}

val listInt = List(1,2,3)
val listString = List("a", "b", "c")

println(whatListAmI(listInt))
println(whatListAmI(listString))

-18

ใช้ยามจับคู่รูปแบบ

    list match  {
        case x:List if x.isInstanceOf(List[String]) => do sth
        case x:List if x.isInstanceOf(List[Int]) => do sth else
     }

4
เหตุผลที่สิ่งนี้ไม่ทำงานคือisInstanceOfทำการตรวจสอบรันไทม์ตามข้อมูลประเภทที่มีอยู่ใน JVM และข้อมูลรันไทม์นั้นจะไม่มีอาร์กิวเมนต์ชนิดถึงList(เนื่องจากการลบประเภท)
Dominique Unruh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.