Dinamik yazım nasıl çalışır ve nasıl kullanılır?


97

DynamicScala'da dinamik yazım yapmanın bir şekilde mümkün olduğunu duydum . Ama bunun nasıl göründüğünü veya nasıl çalıştığını hayal edemiyorum.

Birinin özellikten miras alabileceğini öğrendim Dynamic

class DynImpl extends Dynamic

API kimse bu gibi kullanabilirsiniz söylüyor:

foo.method ("blah") ~~> foo.applyDynamic ("yöntem") ("blah")

Ama denediğimde işe yaramıyor:

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

Bu tamamen mantıklı, çünkü kaynaklara baktıktan sonra bu özelliğin tamamen boş olduğu ortaya çıktı. applyDynamicTanımlanmış bir yöntem yok ve bunu kendi başıma nasıl uygulayacağımı hayal edemiyorum.

Birisi bana işe yaraması için ne yapmam gerektiğini gösterebilir mi?

Yanıtlar:


190

Scalas türü Dynamic, var olmayan nesnelerde yöntem çağırmanıza izin verir veya başka bir deyişle, dinamik dillerde "eksik yöntem" in bir kopyasıdır.

Doğru, scala.Dynamicherhangi bir üyesi yok, sadece bir işaret arayüzü - somut uygulama derleyici tarafından dolduruluyor. Scalas String Interpolation özelliğine gelince , oluşturulan uygulamayı açıklayan iyi tanımlanmış kurallar vardır. Aslında, dört farklı yöntem uygulanabilir:

  • selectDynamic - alan erişimcileri yazılmasına izin verir: foo.bar
  • updateDynamic - alan güncellemelerinin yazılmasına izin verir: foo.bar = 0
  • applyDynamic - argümanlarla metotların çağrılmasına izin verir: foo.bar(0)
  • applyDynamicNamed - adlandırılmış bağımsız değişkenlere sahip yöntemleri çağırmaya izin verir: foo.bar(f = 0)

Bu yöntemlerden birini kullanmak için, genişleyen bir sınıf yazmak Dynamicve yöntemleri orada uygulamak yeterlidir :

class DynImpl extends Dynamic {
  // method implementations here
}

Ayrıca, bir

import scala.language.dynamics

veya -language:dynamicsözellik varsayılan olarak gizlendiğinden derleyici seçeneğini ayarlayın .

selectDynamic

selectDynamicuygulaması en kolay olanıdır. Derleyici bir çağrı çevirir foo.barTo foo.selectDynamic("bar")böylece bu yöntemin bir bekliyor bir argüman listesi olduğu gereklidir, 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

Görüldüğü gibi dinamik metotları açıkça çağırmak da mümkündür.

updateDynamic

Çünkü updateDynamicbu yöntemin dönmesi gereken bir değeri güncellemek için kullanılır Unit. Ayrıca, güncellenecek alanın adı ve değeri, derleyici tarafından farklı argüman listelerine aktarılır:

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

Kod beklendiği gibi çalışır - koda çalışma zamanında yöntemler eklemek mümkündür. Öte yandan, kod artık tür güvenli değildir ve mevcut olmayan bir yöntem çağrılırsa, bu da çalışma zamanında ele alınmalıdır. Ayrıca bu kod dinamik dillerdeki kadar kullanışlı değildir çünkü çalışma zamanında çağrılması gereken yöntemleri oluşturmak mümkün değildir. Bu, böyle bir şey yapamayacağımız anlamına gelir

val name = "foo"
d.$name

çalışma zamanında nereye d.$namedönüştürülür d.foo. Ancak bu o kadar da kötü değil çünkü dinamik dillerde bile bu tehlikeli bir özellik.

Burada dikkat edilmesi gereken bir diğer husus, updateDynamicbirlikte uygulanması gerektiğidir selectDynamic. Bunu yapmazsak, bir derleme hatası alacağız - bu kural, yalnızca aynı ada sahip bir Getter varsa çalışan bir Setter uygulamasına benzer.

applyDynamic

Yöntemleri bağımsız değişkenlerle çağırma yeteneği şu şekilde sağlanır 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

Yöntemin adı ve argümanları yine farklı parametre listelerine ayrılır. İstersek, keyfi sayıda argüman içeren keyfi yöntemleri çağırabiliriz, ancak parantez içermeyen bir yöntemi çağırmak istiyorsak uygulamamız gerekir selectDynamic.

İpucu: Ayrıca, uygulama sözdizimini şu şekilde kullanmak da mümkündür applyDynamic:

scala> d(5)
res1: String = method 'apply' called with arguments '5'

applyDynamicNamed

Mevcut son yöntem, istersek argümanlarımızı adlandırmamıza izin verir:

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

Yöntem imzası fark olduğunu applyDynamicNamedformunun beklediği küpe (String, A)burada Aisteğe bağlı bir türüdür.


Yukarıdaki yöntemlerin hepsinin ortak noktası, parametrelerinin parametrelendirilebilir olmasıdır:

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

Neyse ki, örtük argümanlar eklemek de mümkündür - bir TypeTagbağlam sınırı eklersek , argüman türlerini kolayca kontrol edebiliriz. Ve en iyisi, dönüş türünün bile doğru olmasıdır - bazı yayınlar eklememiz gerekse bile.

Ancak bu tür kusurları aşmanın bir yolu olmadığında Scala, Scala olmazdı. Bizim durumumuzda, yayınlardan kaçınmak için tip sınıflarını kullanabiliriz:

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: _*)
  }

}

Uygulama o kadar güzel görünmese de gücü sorgulanamaz:

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

Her şeyden önce, Dynamicmakrolarla birleştirmek de mümkündür :

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

Makrolar bize tüm derleme süresi garantilerini geri verir ve yukarıdaki durumda o kadar kullanışlı olmasa da, belki bazı Scala DSL'ler için çok yararlı olabilir.

Daha fazla bilgi almak istiyorsanız, daha Dynamicfazla kaynak var:


1
Kesinlikle harika bir cevap ve Scala Power'ın bir vitrini
Herrington Darkholme

Özelliğin varsayılan olarak gizli olması durumunda buna güç demezdim, örneğin deneysel olabilir veya başkalarıyla iyi oynamıyor olabilir mi?
matanster

Scala Dynamic'in performansı hakkında herhangi bir bilgi var mı? Scala Yansımasının yavaş olduğunu biliyorum (bu nedenle Scala-makro gelir). Scala Dynamic kullanımı performansı önemli ölçüde yavaşlatacak mı?
windweller

1
@AllenNie Cevabımda da görebileceğiniz gibi, onu uygulamanın farklı yolları var. Makro kullanırsanız, dinamik çağrı derleme zamanında çözüldüğü için artık ek yük kalmaz. Çalışma zamanında do denetimleri kullanırsanız, doğru kod yoluna doğru şekilde göndermek için parametre denetimi yapmanız gerekir. Bu, uygulamanızdaki diğer herhangi bir parametre kontrolünden daha fazla masraf olmamalıdır. Eğer yansımayı kullanırsanız, açıkça daha fazla yüke sahip olursunuz, ancak uygulamanızı ne kadar yavaşlattığını kendi kendinize ölçmeniz gerekir.
kiritsuku

1
"Makrolar bize tüm derleme süresi garantilerini geri veriyor" - bu aklımı
başımdan alıyor
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.