Scalas 型Dynamic
を使用すると、存在しないオブジェクトのメソッドを呼び出すことができます。つまり、動的言語の「メソッドが見つからない」のレプリカです。
それは正しいです。scala.Dynamic
メンバーはありません。単なるマーカー インターフェイスです。具体的な実装はコンパイラによって埋められます。Scalas String Interpolation機能に関しては、生成された実装を説明する明確に定義されたルールがあります。実際、次の 4 つの異なる方法を実装できます。
selectDynamic
- フィールドアクセサを書くことができます:foo.bar
updateDynamic
- フィールドの更新を書き込むことができます:foo.bar = 0
applyDynamic
- 引数を使用してメソッドを呼び出すことができます:foo.bar(0)
applyDynamicNamed
- 名前付き引数でメソッドを呼び出すことができます:foo.bar(f = 0)
これらのメソッドの 1 つを使用するには、拡張するクラスを作成し、Dynamic
そこにメソッドを実装するだけで十分です。
class DynImpl extends Dynamic {
// method implementations here
}
さらに、追加する必要があります
import scala.language.dynamics
-language:dynamics
または、機能がデフォルトで非表示になっているため、コンパイラ オプションを設定します。
selectDynamic
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
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
whered.$name
は実行時に変換さd.foo
れます。しかし、動的言語でさえこれは危険な機能であるため、これはそれほど悪いことではありません。
ここで注意すべきもう 1 つの点は、 とupdateDynamic
一緒に実装する必要があることselectDynamic
です。これを行わないと、コンパイル エラーが発生します。このルールは、同じ名前の Getter がある場合にのみ機能する Setter の実装に似ています。
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
は、さらにいくつかのリソースがあります。