[解決済み] タイプダイナミックの仕組みと使い方を教えてください。
質問
と聞いたのですが
Dynamic
を使えば、Scala で動的型付けができるらしい。しかし、それがどのように見えるのか、どのように機能するのか、想像がつきません。
trait を継承することができることがわかりました.
Dynamic
class DynImpl extends Dynamic
この API には、このように使えると書いてあります。
foo.method("blah") ~~> foo.applyDynamic("method")("blah")
しかし、実際にやってみるとうまくいきません。
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")
^
これは完全に論理的です。
ソース
を見たところ、この特性は完全に空であることが判明したからです。メソッドはありません。
applyDynamic
というメソッドも定義されておらず、自分で実装する方法が思いつきません。
どなたか、動作させるために必要なことを教えていただけませんか?
どのように解決するのですか?
スカラタイプ
Dynamic
は、存在しないオブジェクトのメソッドを呼び出すことができます。言い換えれば、これは動的言語における "method missing" の複製です。
正しいです。
scala.Dynamic
はメンバを持たず、単なるマーカーインターフェースであり、具体的な実装はコンパイラによって埋められる。スカラについては
文字列の補間
機能に関しては、生成される実装を記述するための明確なルールがある。実際、4つの異なる方法を実装することができます。
-
selectDynamic
- で、フィールドアクセサを記述することができます。foo.bar
-
updateDynamic
- は、フィールドの更新を書き込むことができます。foo.bar = 0
-
applyDynamic
- は、引数付きのメソッドを呼び出すことを可能にします。foo.bar(0)
-
applyDynamicNamed
- は、名前付き引数でメソッドを呼び出すことを可能にします。foo.bar(f = 0)
これらのメソッドのいずれかを使用するには
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
ここで
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
についてさらに情報を得たい場合は、いくつかのリソースがあります。
-
は
公式SIP提案
を導入した
Dynamic
をScalaに導入しました。 - ScalaにおけるDynamic型の実用的な使用法 - SOに関する別の質問(しかし非常に古いです)
関連
-
[解決済み] RDDの内容を印刷するには?
-
[解決済み] ネストした構造体をよりきれいに更新する方法
-
[解決済み] scalaの列挙を理解する
-
[解決済み] Any、AnyVal、AnyRef、Objectの関係と、Javaコードでのマッピングについて教えてください。
-
[解決済み] Scala の private と protected コンストラクタ
-
[解決済み] Scala の Case Classes のオーバーロード・コンストラクタ?
-
[解決済み] 末尾再帰関数が最適化されるためのScalaアノテーションは何ですか?
-
[解決済み] Apache SparkでDataframeのカラム値をListとして抽出する。
-
[解決済み] Scalaでリストを2つのフィールドでソートするには?
-
[解決済み] Scalaのcaseクラスを宣言することのデメリットは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】TypeTagとは何ですか、どのように使用するのですか?
-
[解決済み] Scalaでは、'val a. = _' (アンダースコア)は具体的にどのような意味ですか?A = _' (アンダースコア)とはどういう意味ですか?
-
[解決済み] scalaの列挙を理解する
-
[解決済み] Scalaです。リスト[Future]からFuture[List]への変換は、失敗したFutureを無視する。
-
[解決済み] private[this] vs private
-
[解決済み] sbtとGradleの比較 [終了しました]。
-
[解決済み] Scalaのアクター:受信と反応
-
[解決済み] 機能的デザインパターン【終了しました
-
[解決済み] Scalaでリストを2つのフィールドでソートするには?
-
[解決済み] scala で複数の case class をマッチングさせる