如何inputDynamic以及如何使用它?
我听说在Dynamic
,可以在Scala中进行dynamicinput。 但我无法想象这可能是怎样的,或者它是如何工作的。
我发现一个人可以inheritance特质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
定义,我无法想象如何自己实现它。
有人可以告诉我我需要做些什么才能使其工作?
Scalatypes的Dynamic
允许你调用不存在的对象的方法,或者换句话说,它是dynamic语言中“缺less方法”的复制品。
这是正确的, scala.Dynamic
没有任何成员,它只是一个标记接口 – 具体的实现是由编译器填充。 至于斯卡拉string插值function,有明确的规则描述生成的实现。 实际上,可以实现四种不同的方法:
-
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
因为默认情况下该function是隐藏的。
selectDynamic
selectDynamic
是最容易实现的一个。 编译器将foo.bar
的调用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
可以看到,也可以明确地调用dynamic方法。
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
代码按预期工作 – 可以在运行时向代码添加方法。 另一方面,代码不再是types安全的,如果调用的方法不存在,则必须在运行时处理。 另外,这个代码不像dynamic语言那样有用,因为不可能创build在运行时应该调用的方法。 这意味着我们不能做类似的事情
val name = "foo" d.$name
其中d.$name
将在运行时转换为d.foo
。 但这并不坏,因为即使在dynamic语言中,这也是一个危险的特征。
另外需要注意的是updateDynamic
需要和selectDynamic
一起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
。
提示:也可以在applyDynamic
使用apply-syntax:
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
需要form (String, A)
元组,其中A
是一个任意types。
所有上述方法的共同之处在于它们的参数可以被参数化:
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
上下文绑定,我们可以很容易地检查参数的types。 最好的是即使是返回types也是正确的 – 即使我们不得不添加一些演员。
但是,如果没有办法find解决这些缺陷的方法,Scala就不会成为Scala。 在我们的例子中,我们可以使用types类来避免types转换:
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
与macros结合起来:
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") ^
macros给我们所有的编译时间的保证,虽然它在上述情况下没有用,也许它可能是非常有用的一些斯卡拉DSL。
如果你想获得关于Dynamic
更多信息,还有更多的资源:
- 将
Dynamic
引入Scala的官方SIP提案 - 在Scala中dynamictypes的实际用法 – 另一个关于SO的问题(但非常过时)