डायनेमिक काम कैसे करता है और इसका उपयोग कैसे किया जाता है?


95

मैंने सुना है कि इसके साथ Dynamicस्काला में गतिशील टाइपिंग करना संभव है। लेकिन मैं कल्पना नहीं कर सकता कि यह कैसे दिख सकता है या यह कैसे काम करता है।

मुझे पता चला कि कोई व्यक्ति उत्तराधिकार प्राप्त कर सकता है Dynamic

class DynImpl extends Dynamic

एपीआई का कहना है कि एक इसका उपयोग इस प्रकार कर सकते हैं:

foo.method ("blah") ~~> foo.applyDynamic ("method") ("ah ")

लेकिन जब मैं इसे आज़माता हूँ तो यह काम नहीं करता है:

scala> (new DynImpl).method("blah")
<console>:17: error: value applyDynamic is not a member of DynImpl
error after rewriting to new DynImpl().<applyDynamic: error>("method")
possible cause: maybe a wrong Dynamic method signature?
              (new DynImpl).method("blah")
               ^

यह पूरी तरह से तार्किक है, क्योंकि सूत्रों को देखने के बाद , यह पता चला कि यह विशेषता पूरी तरह से खाली है। कोई विधि applyDynamicपरिभाषित नहीं है और मैं सोच भी नहीं सकता कि इसे कैसे लागू किया जाए।

क्या कोई मुझे दिखा सकता है कि इसे काम करने के लिए मुझे क्या करने की आवश्यकता है?

जवाबों:


188

स्कैलास प्रकार 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कुछ और संसाधनों के बारे में और अधिक जानकारी प्राप्त करना चाहते हैं:


1
निश्चित रूप से एक महान जवाब और स्काला पावर का एक प्रदर्शन
हेरिंगटन डार्कहोल

अगर यह सुविधा डिफ़ॉल्ट रूप से छिपी हो, तो मैं इसे पावर नहीं कहूंगा , उदाहरण के लिए प्रायोगिक हो सकता है या दूसरों के साथ अच्छा नहीं खेल सकता है, या यह नहीं करता है?
मेटनस्टर

क्या स्काला डायनामिक के प्रदर्शन के बारे में कोई जानकारी है? मुझे पता है कि स्काला रिफ्लेक्शन धीमा है (इस प्रकार स्काला-मैक्रो आता है)। क्या स्काला डायनामिक का उपयोग नाटकीय रूप से धीमा होगा?
हवाघर

1
@AllenNie जैसा कि आप मेरे उत्तर में देख सकते हैं, इसे लागू करने के विभिन्न तरीके हैं। यदि आप मैक्रोज़ का उपयोग करते हैं, तो अब कोई ओवरहेड नहीं है, क्योंकि गतिशील कॉल को संकलन समय पर हल किया जाता है। यदि आप रनटाइम पर चेक का उपयोग करते हैं, तो आपको सही कोड पथ पर सही तरीके से भेजने के लिए पैरामीटर चेकिंग करनी होगी। आपके एप्लिकेशन में किसी भी अन्य पैरामीटर की जाँच करने से अधिक ओवरहेड नहीं होना चाहिए। यदि आप प्रतिबिंब का उपयोग करते हैं, तो आप स्पष्ट रूप से अधिक उपरि हो जाते हैं, लेकिन आपको खुद से मापना होगा कि यह आपके आवेदन को कितना धीमा करता है।
kiritsuku

1
"मैक्रोज़ ने हमें सभी संकलन समय की गारंटी दी है" - यह मेरे दिमाग को उड़ा रहा है
tksfz
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.