我听说有Dynamic
它是某种可以做到动态类型在Scala中。 但是,我怎么也想不到的是看起来像或者它是如何工作。
我发现一个可以从特质继承Dynamic
class DynImpl extends Dynamic
该API称,可以使用这样的:
foo.method( “等等”)~~> foo.applyDynamic( “方法”)( “等等”)
但是,当我尝试,它不工作:
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
定义,我无法想象如何通过自己实现它。
可有人告诉我,我需要做的,它的工作?
Scalas型Dynamic
允许你呼吁不存在或者换句话说,它是动态语言法“失踪”的翻版对象的方法。
这是正确的, scala.Dynamic
没有任何成员,它只是一个标记接口-具体执行是填写的编译器。 至于Scalas 字符串插值功能有描述生成的实现明确的规则。 事实上,一个可以实现四种不同的方法:
-
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
。 如果我们不这样做,我们将得到一个编译错误 - 这条规则是类似于一个二传手,这只有在有相同名称的吸气工程的实施。
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
:
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
约束,我们可以很容易地检查类型的参数范围内。 最重要的事是,即使返回类型是正确的 - 尽管我们不得不增加一些转换。
但是斯卡拉不会斯卡拉时,有没有办法找到解决这种缺陷的方法。 在我们的例子中,我们可以使用类型类,以避免强制转换:
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")
^
宏还给我们所有的编译时间保证,虽然它是不是在上述情况下有用,也许它可以为一些DSL的斯卡拉非常有用的。
如果你想获得更多的信息Dynamic
也有一些更多的资源:
- 该负责人建议,SIP介绍了
Dynamic
到斯卡拉 - Scala中的一个动态类型的实际用途 -在SO另一个问题(但非常过时)