টাইপ ডায়নামিক কীভাবে কাজ করে এবং কীভাবে এটি ব্যবহার করবে?


97

শুনেছি Dynamicস্কালায় ডায়নামিক টাইপিং করা কোনওরকমভাবেই সম্ভব। তবে কীভাবে এটি দেখতে বা এটি কীভাবে কাজ করে তা আমি ভাবতে পারি না।

আমি জানতে পেরেছি যে কেউ বৈশিষ্ট্য থেকে উত্তরাধিকারী হতে পারে Dynamic

class DynImpl extends Dynamic

এপিআই বলছেন যে এক এটা ভালো ব্যবহার করতে পারেন:

foo.method ("blah") ~~> foo.applyDynamic ("পদ্ধতি") ("ব্লাহ")

তবে যখন আমি চেষ্টা করে দেখি এটি কার্যকর হয় না:

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নির্ধারিত নেই এবং আমি নিজে থেকে কীভাবে এটি প্রয়োগ করব তা কল্পনা করতে পারি না।

এটির কাজটি করার জন্য আমাকে কী করা দরকার তা কেউ আমাকে দেখাতে পারে?

উত্তর:


190

স্ক্যালাস টাইপ 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বাস্তবায়ন সবচেয়ে সহজ এক। সংকলক একটি কল অনুবাদ 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এই পদ্ধতিটি ফিরে আসতে হবে এমন একটি মান আপডেট করতে ব্যবহৃত হয় 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:

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'

প্রয়োগডায়নামিক নাম

সর্বশেষ উপলভ্য পদ্ধতিটি আমাদের চাইলে আমাদের যুক্তিগুলির নাম দেওয়ার অনুমতি দেয়:

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ফর্মের টিপলগুলি (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:


4
অবশ্যই একটি দুর্দান্ত উত্তর এবং স্কালা পাওয়ারের একটি শোকেস
হেরিংটন দারখোলমে

বৈশিষ্ট্যটি ডিফল্টরূপে লুকানো থাকলে আমি এটিকে শক্তি বলব না , উদাহরণস্বরূপ পরীক্ষামূলক হতে পারে বা অন্যের সাথে ভাল না খেলতে পারে, না হয়?
ম্যাটানস্টার

স্কালা ডায়নামিকের পারফরম্যান্স সম্পর্কে কোনও তথ্য আছে? আমি জানি স্কালাল প্রতিবিম্বটি ধীর (এইভাবে স্কেলা-ম্যাক্রো আসে)। স্কালা ডায়নামিকের ব্যবহার নাটকীয়ভাবে পারফরম্যান্সকে কমিয়ে দেবে?
বায়ুচালক

4
@ অ্যালেননি যেমন আপনি আমার উত্তরটিতে দেখতে পাচ্ছেন, এটি কার্যকর করার বিভিন্ন উপায় রয়েছে। আপনি যদি ম্যাক্রোগুলি ব্যবহার করেন তবে আর কোনও ওভারহেড নেই, যেহেতু গতিশীল কলটি সংকলন সময়ে সমাধান করা হয়েছে। আপনি যদি রানটাইমের সময় ড চেক ব্যবহার করেন তবে সঠিক কোডের পথে সঠিকভাবে প্রেরণের জন্য আপনাকে প্যারামিটার চেকিং করতে হবে। আপনার অ্যাপ্লিকেশনটিতে যে কোনও প্যারামিটার চেক করা উচিত তার চেয়ে বেশি ওভারহেড হওয়া উচিত নয়। আপনি যদি প্রতিবিম্বটি ব্যবহার করেন তবে আপনি স্পষ্টতই বেশি ওভারহেড পাবেন তবে এটি আপনার অ্যাপ্লিকেশনটি কতটা কমিয়ে দেয় তা আপনাকে নিজেই মাপতে হবে।
কিরিতসুকু

4
"ম্যাক্রোস আমাদের সমস্ত সংকলনের সময় গ্যারান্টি ফিরিয়ে দেয়" - এটি আমার মনকে উড়িয়ে
দিচ্ছে
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.