Jenis Scalas Dynamic
memungkinkan Anda memanggil metode pada objek yang tidak ada atau dengan kata lain merupakan replika "metode yang hilang" dalam bahasa dinamis.
Benar, scala.Dynamic
tidak memiliki anggota, ini hanya antarmuka penanda - implementasi konkret diisi oleh kompiler. Adapun fitur Interpolasi String Scalas ada aturan yang didefinisikan dengan baik yang menjelaskan implementasi yang dihasilkan. Faktanya, seseorang dapat menerapkan empat metode berbeda:
selectDynamic
- memungkinkan untuk menulis pengakses bidang: foo.bar
updateDynamic
- memungkinkan untuk menulis pembaruan lapangan: foo.bar = 0
applyDynamic
- memungkinkan untuk memanggil metode dengan argumen: foo.bar(0)
applyDynamicNamed
- memungkinkan untuk memanggil metode dengan argumen bernama: foo.bar(f = 0)
Untuk menggunakan salah satu metode ini, cukup menulis kelas yang diperluas Dynamic
dan mengimplementasikan metode di sana:
class DynImpl extends Dynamic {
// method implementations here
}
Selanjutnya perlu menambahkan file
import scala.language.dynamics
atau setel opsi kompiler -language:dynamics
karena fitur tersebut tersembunyi secara default.
selectDynamic
selectDynamic
adalah yang termudah untuk diterapkan. Kompilator menerjemahkan panggilan foo.bar
ke foo.selectDynamic("bar")
, oleh karena itu metode ini diharuskan memiliki daftar argumen yang mengharapkan 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
Seperti yang bisa dilihat, metode dinamis juga dapat dipanggil secara eksplisit.
updateDynamic
Karena updateDynamic
digunakan untuk memperbarui nilai yang perlu dikembalikan metode ini Unit
. Selanjutnya, nama bidang yang akan diperbarui dan nilainya diteruskan ke daftar argumen yang berbeda oleh kompilator:
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
Kode berfungsi seperti yang diharapkan - dimungkinkan untuk menambahkan metode pada waktu proses ke kode. Di sisi lain, kode tersebut tidak lagi aman untuk diketik dan jika sebuah metode dipanggil tetapi tidak ada, ini harus ditangani pada waktu proses juga. Selain itu, kode ini tidak berguna seperti dalam bahasa dinamis karena tidak mungkin membuat metode yang harus dipanggil saat runtime. Artinya kita tidak bisa melakukan sesuatu seperti itu
val name = "foo"
d.$name
di mana d.$name
akan diubah menjadi d.foo
saat runtime. Tetapi ini tidak terlalu buruk karena bahkan dalam bahasa dinamis ini adalah fitur yang berbahaya.
Hal lain yang perlu diperhatikan disini, adalah yang updateDynamic
perlu diimplementasikan bersama selectDynamic
. Jika kita tidak melakukan ini, kita akan mendapatkan kesalahan kompilasi - aturan ini mirip dengan implementasi Setter, yang hanya berfungsi jika ada Getter dengan nama yang sama.
applyDynamic
Kemampuan untuk memanggil metode dengan argumen disediakan oleh 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
Nama metode dan argumennya lagi-lagi dipisahkan ke daftar parameter yang berbeda. Kita dapat memanggil metode arbitrer dengan sejumlah argumen jika kita mau, tetapi jika kita ingin memanggil metode tanpa tanda kurung, kita perlu menerapkannya selectDynamic
.
Petunjuk: Dimungkinkan juga untuk menggunakan apply-syntax dengan applyDynamic
:
scala> d(5)
res1: String = method 'apply' called with arguments '5'
applyDynamicNamed
Metode terakhir yang tersedia memungkinkan kita memberi nama argumen kita jika kita ingin:
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)'
Perbedaan dalam tanda tangan metode adalah yang applyDynamicNamed
mengharapkan tupel dari bentuk di (String, A)
mana A
merupakan tipe arbitrer.
Semua metode di atas memiliki kesamaan bahwa parameternya dapat dijadikan parameter:
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
Untungnya, dimungkinkan juga untuk menambahkan argumen implisit - jika kita menambahkan TypeTag
ikatan konteks, kita dapat dengan mudah memeriksa tipe argumen. Dan yang terbaik adalah bahwa bahkan tipe pengembaliannya benar - meskipun kami harus menambahkan beberapa gips.
Tetapi Scala tidak akan menjadi Scala jika tidak ada cara untuk menemukan cara mengatasi kekurangan tersebut. Dalam kasus kami, kami dapat menggunakan kelas tipe untuk menghindari gips:
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: _*)
}
}
Meskipun implementasinya tidak terlihat bagus, kekuatannya tidak dapat dipertanyakan:
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
Di atas semua, dimungkinkan juga untuk menggabungkan Dynamic
dengan makro:
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")
^
Makro memberi kita kembali semua jaminan waktu kompilasi dan meskipun tidak begitu berguna dalam kasus di atas, mungkin ini bisa sangat berguna untuk beberapa Scala DSL.
Jika Anda ingin mendapatkan lebih banyak informasi tentang Dynamic
ada lebih banyak sumber: