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.Dynamic
herhangi 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 Dynamic
ve yöntemleri orada uygulamak yeterlidir :
class DynImpl extends Dynamic {
}
Ayrıca, bir
import scala.language.dynamics
veya -language:dynamics
özellik varsayılan olarak gizlendiğinden derleyici seçeneğini ayarlayın .
selectDynamic
selectDynamic
uygulaması en kolay olanıdır. Derleyici bir çağrı çevirir foo.bar
To 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ü updateDynamic
bu 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.$name
dö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, updateDynamic
birlikte 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 applyDynamicNamed
formunun beklediği küpe (String, A)
burada A
isteğ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 TypeTag
bağ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, Dynamic
makrolarla 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 Dynamic
fazla kaynak var: