ประเภท Scalas Dynamic
ช่วยให้คุณสามารถเรียกใช้เมธอดบนวัตถุที่ไม่มีอยู่หรืออีกนัยหนึ่งก็คือแบบจำลองของ "method missing" ในภาษาไดนามิก
ถูกต้องscala.Dynamic
ไม่มีสมาชิกเป็นเพียงอินเทอร์เฟซเครื่องหมาย - การใช้งานที่เป็นรูปธรรมถูกเติมเต็มโดยคอมไพเลอร์ สำหรับคุณลักษณะการแก้ไขสตริง Scalas มีกฎที่กำหนดไว้อย่างดีซึ่งอธิบายถึงการนำไปใช้งานที่สร้างขึ้น ในความเป็นจริงหนึ่งสามารถใช้สี่วิธีที่แตกต่างกัน:
selectDynamic
- อนุญาตให้เขียนตัวเข้าถึงฟิลด์: foo.bar
updateDynamic
- อนุญาตให้เขียนการอัปเดตฟิลด์: foo.bar = 0
applyDynamic
- อนุญาตให้เรียกเมธอดพร้อมอาร์กิวเมนต์: foo.bar(0)
applyDynamicNamed
- อนุญาตให้เรียกวิธีการด้วยอาร์กิวเมนต์ที่ตั้งชื่อ: foo.bar(f = 0)
ในการใช้หนึ่งในวิธีการเหล่านี้ก็เพียงพอที่จะเขียนคลาสที่ขยายDynamic
และใช้วิธีการที่นั่น:
class DynImpl extends Dynamic {
}
นอกจากนี้ยังต้องเพิ่มไฟล์
import scala.language.dynamics
หรือตั้งค่าตัวเลือกคอมไพเลอร์-language:dynamics
เนื่องจากคุณลักษณะนี้ถูกซ่อนไว้โดยค่าเริ่มต้น
selectDynamic
selectDynamic
เป็นวิธีที่ง่ายที่สุดในการนำไปใช้ คอมไพเลอร์แปล call of foo.bar
to foo.selectDynamic("bar")
ดังนั้นจึงจำเป็นต้องใช้เมธอดนี้มีรายการอาร์กิวเมนต์ที่คาดหวัง a 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
หากเราไม่ทำเช่นนี้เราจะได้รับข้อผิดพลาดในการคอมไพล์ - กฎนี้คล้ายกับการใช้งาน Setter ซึ่งใช้ได้เฉพาะเมื่อมี Getter ที่มีชื่อเดียวกัน
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
ที่เราจำเป็นต้องใช้
คำแนะนำ: นอกจากนี้ยังสามารถใช้ Apply-syntax กับ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
คาดว่าสิ่ง(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
บริบทที่ถูกผูกไว้เราสามารถตรวจสอบประเภทของอาร์กิวเมนต์ได้อย่างง่ายดาย และสิ่งที่ดีที่สุดคือถึงแม้ประเภทการส่งคืนจะถูกต้อง - แม้ว่าเราจะต้องเพิ่มการร่ายบ้าง
แต่ Scala จะไม่ใช่ Scala เมื่อไม่มีทางหาทางแก้ไขข้อบกพร่องดังกล่าวได้ ในกรณีของเราเราสามารถใช้คลาสประเภทเพื่อหลีกเลี่ยงการร่าย:
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")
^
มาโครให้การรับประกันเวลาคอมไพล์ทั้งหมดแก่เราและแม้ว่ามันจะไม่มีประโยชน์ในกรณีข้างต้น แต่บางทีมันอาจมีประโยชน์มากสำหรับ Scala DSL บางตัว
หากคุณต้องการรับข้อมูลเพิ่มเติมเกี่ยวกับDynamic
แหล่งข้อมูลเพิ่มเติม: