स्कैलास प्रकार Dynamic
आपको उन वस्तुओं पर विधियों को कॉल करने की अनुमति देता है जो मौजूद नहीं हैं या दूसरे शब्दों में यह गतिशील भाषाओं में "लापता" विधि की प्रतिकृति है।
यह सही है, scala.Dynamic
इसका कोई सदस्य नहीं है, यह सिर्फ एक मार्कर इंटरफ़ेस है - संकलक द्वारा कंक्रीट कार्यान्वयन भरा हुआ है। स्कालास स्ट्रिंग इंटरपोलेशन फ़ीचर के रूप में उत्पन्न कार्यान्वयन का वर्णन करने वाले अच्छी तरह से परिभाषित नियम हैं। वास्तव में, कोई चार अलग-अलग तरीकों को लागू कर सकता है:
selectDynamic
- फ़ील्ड एक्सेसर्स लिखने की अनुमति देता है: foo.bar
updateDynamic
- क्षेत्र अपडेट लिखने की अनुमति देता है: foo.bar = 0
applyDynamic
- तर्कों के साथ तरीकों को कॉल करने की अनुमति देता है: foo.bar(0)
applyDynamicNamed
- नामित तर्कों के साथ तरीकों को कॉल करने की अनुमति देता है: foo.bar(f = 0)
इन विधियों में से किसी एक का उपयोग करने के लिए यह एक वर्ग लिखने के लिए पर्याप्त है जो फैली हुई है Dynamic
और वहां विधियों को लागू करने के लिए:
class DynImpl extends Dynamic {
// method implementations here
}
इसके अलावा एक को जोड़ने की जरूरत है
import scala.language.dynamics
या संकलक विकल्प सेट करें -language:dynamics
क्योंकि सुविधा डिफ़ॉल्ट रूप से छिपी हुई है।
selectDynamic
selectDynamic
लागू करने के लिए सबसे आसान है। कंपाइलर की एक कॉल का अनुवाद करता foo.bar
है foo.selectDynamic("bar")
, इस प्रकार यह आवश्यक है कि इस विधि में तर्क सूची की अपेक्षा है String
:
class DynImpl extends Dynamic {
def selectDynamic(name: String) = name
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@6040af64
scala> d.foo
res37: String = foo
scala> d.bar
res38: String = bar
scala> d.selectDynamic("foo")
res54: String = foo
जैसा कि एक देख सकता है, गतिशील तरीकों को स्पष्ट रूप से कॉल करना भी संभव है।
updateDynamic
क्योंकि updateDynamic
एक मान को अद्यतन करने के लिए उपयोग किया जाता है इस पद्धति को वापस करने की आवश्यकता है Unit
। इसके अलावा, अद्यतन करने के लिए फ़ील्ड का नाम और उसके मान को कंपाइलर द्वारा अलग-अलग तर्क सूचियों में दिया जाता है:
class DynImpl extends Dynamic {
var map = Map.empty[String, Any]
def selectDynamic(name: String) =
map get name getOrElse sys.error("method not found")
def updateDynamic(name: String)(value: Any) {
map += name -> value
}
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@7711a38f
scala> d.foo
java.lang.RuntimeException: method not found
scala> d.foo = 10
d.foo: Any = 10
scala> d.foo
res56: Any = 10
कोड अपेक्षित रूप से काम करता है - कोड में रनटाइम पर विधियों को जोड़ना संभव है। दूसरी तरफ, कोड अब टाइप नहीं है और अगर कोई विधि कहलाती है तो इसका अस्तित्व रनटाइम पर भी नहीं होना चाहिए। इसके अलावा यह कोड गतिशील भाषाओं में उतना उपयोगी नहीं है क्योंकि यह उन तरीकों को बनाना संभव नहीं है जिन्हें रनटाइम पर बुलाया जाना चाहिए। इसका मतलब है कि हम ऐसा कुछ नहीं कर सकते
val name = "foo"
d.$name
जहां रनटाइम में d.$name
तब्दील हो जाएगा d.foo
। लेकिन यह इतना बुरा नहीं है क्योंकि गतिशील भाषाओं में भी यह एक खतरनाक विशेषता है।
यहां एक और बात ध्यान देने वाली है, वह यह है कि इसे updateDynamic
एक साथ लागू किया जाना चाहिए selectDynamic
। यदि हम ऐसा नहीं करते हैं तो हमें एक संकलित त्रुटि मिलेगी - यह नियम एक सेटर के कार्यान्वयन के समान है, जो केवल उसी नाम से एक गेट्टर होने पर काम करता है।
applyDynamic
तर्कों के साथ तरीकों को कॉल करने की क्षमता applyDynamic
निम्न द्वारा प्रदान की जाती है :
class DynImpl extends Dynamic {
def applyDynamic(name: String)(args: Any*) =
s"method '$name' called with arguments ${args.mkString("'", "', '", "'")}"
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@766bd19d
scala> d.ints(1, 2, 3)
res68: String = method 'ints' called with arguments '1', '2', '3'
scala> d.foo()
res69: String = method 'foo' called with arguments ''
scala> d.foo
<console>:19: error: value selectDynamic is not a member of DynImpl
विधि का नाम और उसके तर्क फिर से अलग-अलग पैरामीटर सूचियों में अलग हो जाते हैं। हम चाहें तो मनमाने तरीके से तर्कों के साथ मनमाने तरीके से कॉल कर सकते हैं लेकिन अगर हम बिना किसी कोष्ठक के एक विधि को कॉल करना चाहते हैं तो हमें लागू करने की आवश्यकता है selectDynamic
।
संकेत: इसके साथ आवेदन-वाक्यविन्यास का उपयोग करना भी संभव है applyDynamic
:
scala> d(5)
res1: String = method 'apply' called with arguments '5'
applyDynamicNamed
यदि हम चाहें तो अंतिम उपलब्ध विधि हमें अपने तर्कों का नाम देने की अनुमति देती है:
class DynImpl extends Dynamic {
def applyDynamicNamed(name: String)(args: (String, Any)*) =
s"method '$name' called with arguments ${args.mkString("'", "', '", "'")}"
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@123810d1
scala> d.ints(i1 = 1, i2 = 2, 3)
res73: String = method 'ints' called with arguments '(i1,1)', '(i2,2)', '(,3)'
विधि हस्ताक्षर में अंतर यह है कि जहां एक मनमाना प्रकार है applyDynamicNamed
, उस रूप के tuples की अपेक्षा करता है।(String, A)
A
उपरोक्त सभी विधियां आम हैं कि उनके मापदंडों को मानकीकृत किया जा सकता है:
class DynImpl extends Dynamic {
import reflect.runtime.universe._
def applyDynamic[A : TypeTag](name: String)(args: A*): A = name match {
case "sum" if typeOf[A] =:= typeOf[Int] =>
args.asInstanceOf[Seq[Int]].sum.asInstanceOf[A]
case "concat" if typeOf[A] =:= typeOf[String] =>
args.mkString.asInstanceOf[A]
}
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@5d98e533
scala> d.sum(1, 2, 3)
res0: Int = 6
scala> d.concat("a", "b", "c")
res1: String = abc
सौभाग्य से, निहित तर्क को जोड़ना भी संभव है - यदि हम एक TypeTag
संदर्भ जोड़ते हैं तो हम आसानी से तर्कों के प्रकारों की जांच कर सकते हैं। और सबसे अच्छी बात यह है कि वापसी का प्रकार भी सही है - भले ही हमें कुछ जातियों को जोड़ना पड़ा हो।
लेकिन स्काला तब स्केला नहीं होगा जब इस तरह की खामियों के आसपास कोई रास्ता नहीं होगा। हमारे मामले में हम जातियों से बचने के लिए टाइप कक्षाओं का उपयोग कर सकते हैं:
object DynTypes {
sealed abstract class DynType[A] {
def exec(as: A*): A
}
implicit object SumType extends DynType[Int] {
def exec(as: Int*): Int = as.sum
}
implicit object ConcatType extends DynType[String] {
def exec(as: String*): String = as.mkString
}
}
class DynImpl extends Dynamic {
import reflect.runtime.universe._
import DynTypes._
def applyDynamic[A : TypeTag : DynType](name: String)(args: A*): A = name match {
case "sum" if typeOf[A] =:= typeOf[Int] =>
implicitly[DynType[A]].exec(args: _*)
case "concat" if typeOf[A] =:= typeOf[String] =>
implicitly[DynType[A]].exec(args: _*)
}
}
हालांकि कार्यान्वयन अच्छा नहीं लगता है, लेकिन इसकी शक्ति पर सवाल नहीं उठाया जा सकता है:
scala> val d = new DynImpl
d: DynImpl = DynImpl@24a519a2
scala> d.sum(1, 2, 3)
res89: Int = 6
scala> d.concat("a", "b", "c")
res90: String = abc
सबसे ऊपर, Dynamic
मैक्रोज़ के साथ संयोजन करना भी संभव है :
class DynImpl extends Dynamic {
import language.experimental.macros
def applyDynamic[A](name: String)(args: A*): A = macro DynImpl.applyDynamic[A]
}
object DynImpl {
import reflect.macros.Context
import DynTypes._
def applyDynamic[A : c.WeakTypeTag](c: Context)(name: c.Expr[String])(args: c.Expr[A]*) = {
import c.universe._
val Literal(Constant(defName: String)) = name.tree
val res = defName match {
case "sum" if weakTypeOf[A] =:= weakTypeOf[Int] =>
val seq = args map(_.tree) map { case Literal(Constant(c: Int)) => c }
implicitly[DynType[Int]].exec(seq: _*)
case "concat" if weakTypeOf[A] =:= weakTypeOf[String] =>
val seq = args map(_.tree) map { case Literal(Constant(c: String)) => c }
implicitly[DynType[String]].exec(seq: _*)
case _ =>
val seq = args map(_.tree) map { case Literal(Constant(c)) => c }
c.abort(c.enclosingPosition, s"method '$defName' with args ${seq.mkString("'", "', '", "'")} doesn't exist")
}
c.Expr(Literal(Constant(res)))
}
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@c487600
scala> d.sum(1, 2, 3)
res0: Int = 6
scala> d.concat("a", "b", "c")
res1: String = abc
scala> d.noexist("a", "b", "c")
<console>:11: error: method 'noexist' with args 'a', 'b', 'c' doesn't exist
d.noexist("a", "b", "c")
^
मैक्रोज़ हमें सभी संकलन समय की गारंटी देते हैं और जबकि यह उपरोक्त मामले में उपयोगी नहीं है, हो सकता है कि यह कुछ स्काला डीएसएल के लिए बहुत उपयोगी हो।
यदि आप Dynamic
कुछ और संसाधनों के बारे में और अधिक जानकारी प्राप्त करना चाहते हैं: