scala - मैं स्कैला पर चारों ओर मिरर कैसे प्राप्त करूं? या, मुझे अपने संग्रह का प्रकार पैरामीटर क्यों नहीं मिल सकता है?




type-erasure (10)

यह स्कैला पर जीवन का एक दुखद तथ्य है कि यदि आप एक सूची [Int] को तुरंत चालू करते हैं, तो आप यह सत्यापित कर सकते हैं कि आपका उदाहरण एक सूची है, और आप यह सत्यापित कर सकते हैं कि इसका कोई भी व्यक्तिगत तत्व एक इंट है, लेकिन यह नहीं कि यह एक सूची है [ 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?!

अनचेक विकल्प टाइप एरर पर स्क्वायरली रूप से दोष डालता है:

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

वह क्यों है, और मैं इसके आसपास कैसे हो सकता हूं?


Answers

मैं एक अपेक्षाकृत सरल समाधान के साथ आया जो सीमित उपयोग स्थितियों में पर्याप्त होगा, अनिवार्य रूप से पैरामीटरयुक्त प्रकारों को लपेटना जो रैपर वर्गों में टाइप एरर समस्या से ग्रस्त होंगे, जिनका मिलान मैच स्टेटमेंट में किया जा सकता है।

case class StringListHolder(list:List[String])

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

इसने अपेक्षित आउटपुट और हमारे केस क्लास की वांछित प्रकार, स्ट्रिंग सूचियों की सामग्री को सीमित कर दिया है।

यहां अधिक जानकारी: http://www.scalafied.com/?p=60


मैं सोच रहा हूं कि यह एक उपयुक्त कामकाज है:

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

क्या यह थोड़ी बेहतर नहीं है या क्या मैं यहां बिंदु खो रहा हूं?


एक समाधान नहीं बल्कि इसके साथ रहने के लिए एक रास्ता पूरी तरह से गलीचा के बिना इसे @unchecked करने का एक तरीका: @unchecked एनोटेशन जोड़ना। यहां देखें - http://www.scala-lang.org/api/current/index.html#scala.unchecked


परिणाम के बाद आप टाइप करने Typeable टाइप क्लास को Typeable उपयोग कर सकते हैं,

नमूना आरईपीएल सत्र,

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 ऑपरेशन यथासंभव सटीक wrt Typeable रूप में संभव होगा, इन-स्कोप Typeable करने Typeable उदाहरण उपलब्ध हैं।


आप टाइपटैग का उपयोग करके ऐसा कर सकते हैं (जैसा कि डैनियल पहले से ही उल्लेख करता है, लेकिन मैं इसे स्पष्ट रूप से स्पेल कर दूंगा):

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

आप क्लासटैग का उपयोग करके भी ऐसा कर सकते हैं (जो आपको स्कैला-प्रतिबिंबित करने पर निर्भर करता है):

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

क्लासटैग का उपयोग तब तक किया जा सकता है जब आप टाइप पैरामीटर A अपेक्षा नहीं करते हैं कि यह एक सामान्य प्रकार हो।

दुर्भाग्यवश यह एक छोटी वर्बोज़ है और आपको एक कंपाइलर चेतावनी दबाने के लिए @unchecked एनोटेशन की आवश्यकता है। टाइपटाग को भविष्य में कंपाइलर द्वारा स्वचालित रूप से पैटर्न मिलान में शामिल किया जा सकता है: https://issues.scala-lang.org/browse/SI-6517


मुझे अन्यथा भयानक भाषा की इस सीमा के लिए थोड़ा बेहतर कामकाज मिला।

स्कैला में, टाइप एरर का मुद्दा एरे के साथ नहीं होता है। मुझे लगता है कि इसे एक उदाहरण के साथ प्रदर्शित करना आसान है।

आइए मान लें कि हमारे पास (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)] का उपयोग करके case a:Array[(Int, String)] अभी भी एक प्रकार की मिरर चेतावनी देगा, इसलिए एक नए कंटेनर क्लास (इस उदाहरण में, IntString ) का उपयोग करना आवश्यक है।


पैटर्न मैच गार्ड का उपयोग करना

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

यह उत्तर Manifest -API का उपयोग करता है, जिसे स्कैला 2.10 के रूप में बहिष्कृत किया गया है। अधिक मौजूदा समाधानों के लिए कृपया नीचे दिए गए उत्तरों देखें।

स्कैला को टाइप एरर के साथ परिभाषित किया गया था क्योंकि जावा वर्चुअल मशीन (जेवीएम), जावा के विपरीत, जेनेरिक नहीं मिला। इसका मतलब है कि, रन टाइम पर, केवल वर्ग मौजूद है, न कि इसके प्रकार पैरामीटर। उदाहरण में, JVM जानता है कि यह scala.collection.immutable.List को संभालने वाला है, लेकिन यह नहीं कि यह सूची Int साथ पैरामीटरकृत है।

सौभाग्य से, स्कैला में एक सुविधा है जो आपको इसके चारों ओर घूमने देती है। यह प्रकट है । एक अभिव्यक्ति वर्ग है जिसका उदाहरण वस्तुओं का प्रतिनिधित्व करने वाले वस्तुएं हैं। चूंकि ये उदाहरण वस्तुएं हैं, इसलिए आप उन्हें पास कर सकते हैं, उन्हें स्टोर कर सकते हैं, और आम तौर पर उन पर विधियों को कॉल कर सकते हैं। निहित मापदंडों के समर्थन के साथ, यह एक बहुत शक्तिशाली उपकरण बन जाता है। उदाहरण के लिए, निम्न उदाहरण लें:

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

तत्व को संग्रहीत करते समय, हम इसके "मैनिफेस्ट" को भी स्टोर करते हैं। एक घोषणापत्र एक वर्ग है जिसका उदाहरण स्कैला प्रकारों का प्रतिनिधित्व करता है। इन ऑब्जेक्ट्स में JVM की तुलना में अधिक जानकारी है, जो हमें पूर्ण, पैरामीटर प्रकार के लिए परीक्षण करने में सक्षम बनाता है।

नोट, हालांकि, एक Manifest अभी भी एक विकसित विशेषता है। इसकी सीमाओं के उदाहरण के रूप में, वर्तमान में यह भिन्नता के बारे में कुछ भी नहीं जानता है, और मानता है कि सब कुछ सह-भिन्नता है। मुझे आशा है कि स्काला प्रतिबिंब पुस्तकालय, वर्तमान में विकास के तहत, समाप्त होने के बाद यह अधिक स्थिर और ठोस हो जाएगा।


मैं एक ऐसा उत्तर जोड़ना चाहता था जो समस्या को सामान्य करता है: रनटाइम पर मेरी सूची के प्रकार का स्ट्रिंग प्रस्तुति कैसे प्राप्त करें

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

नोट: आपके पास माइल्स सबिन की बेकार लाइब्रेरी के साथ एक विकल्प भी है ( 2012 में माइल्स द्वारा पहले ही उल्लेख किया गया है )।

आप जाको पल्लारी से " स्केल में जेनेरिक प्रकारों के सामान्य प्रकार के मिलान " में एक उदाहरण देख सकते हैं

Typeable योग्य एक प्रकार का वर्ग है जो Any प्रकार से किसी विशिष्ट प्रकार से मूल्यों को कास्ट करने की क्षमता प्रदान करता है
कास्टिंग ऑपरेशन का नतीजा एक Option जहां Some मूल्य में सफलतापूर्वक कास्ट किया गया मान होगा, और None मूल्य एक कास्ट विफलता का प्रतिनिधित्व None करता है।

TypeCase पुल Typeable और पैटर्न मिलान। यह अनिवार्य रूप से Typeable उदाहरणों के लिए एक निकालने वाला है

import shapeless._

def extractCollection[T: Typeable](a: Any): Option[Iterable[T]] = {
  val list = TypeCase[List[T]]
  val set  = TypeCase[Set[T]]
  a match {
    case list(l) => Some(l)
    case set(s)  => Some(s)
    case _       => None
  }
}

val l1: Any = List(1, 2, 3)
val l2: Any = List[Int]()
val s:  Any = Set(1, 2, 3)

extractCollection[Int](l1)    // Some(List(1, 2, 3))
extractCollection[Int](s)     // Some(Set(1, 2, 3))
extractCollection[String](l1) // None
extractCollection[String](s)  // None
extractCollection[String](l2) // Some(List()) // Shouldn't this be None? We'll get back to this.

टाइप करने योग्य ऐसा लगता है कि टाइप Typeable को हल करने में क्या होता है, यह अभी भी किसी भी अन्य रनटाइम कोड के समान व्यवहार के अधीन है।
यह पिछले कोड उदाहरणों की आखिरी पंक्तियों में देखा जा सकता है जहां रिक्त सूचियों को स्ट्रिंग सूचियों के रूप में पहचाना जाता था, भले ही उन्हें पूर्णांक सूचियों के लिए निर्दिष्ट किया गया हो। ऐसा इसलिए है क्योंकि Typeable कास्ट सूची के मूल्यों पर आधारित होते हैं। यदि सूची खाली है, तो स्वाभाविक रूप से यह एक वैध स्ट्रिंग सूची और वैध पूर्णांक सूची (या उस मामले के लिए कोई अन्य सूची) है





scala type-erasure