scala 动态类型如何工作以及如何使用它?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15799811/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How does type Dynamic work and how to use it?
提问by kiritsuku
I heard that with Dynamicit is somehow possible to do dynamic typing in Scala. But I can't imagine how that might look like or how it works.
我听说Dynamic在 Scala 中可以以某种方式进行动态输入。但我无法想象它会是什么样子或它是如何工作的。
I found out that one can inherit from trait Dynamic
我发现可以从 trait 继承 Dynamic
class DynImpl extends Dynamic
The APIsays that one can use it like this:
该API称,可以使用这样的:
foo.method("blah") ~~> foo.applyDynamic("method")("blah")
foo.method("blah") ~~> foo.applyDynamic("method")("blah")
But when I try it out it doesn't work:
但是当我尝试它时它不起作用:
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")
^
This is completely logical, because after looking to the sources, it turned out that this trait is completely empty. There is no method applyDynamicdefined and I can't imagine how to implement it by myself.
这是完全合乎逻辑的,因为观察到之后的来源,事实证明,这种特质完全是空的。没有applyDynamic定义方法,我无法想象如何自己实现它。
Can someone show me what I need to do to make it to work?
有人可以告诉我我需要做什么才能让它工作吗?
回答by kiritsuku
Scalas type Dynamicallows you to call methods on objects that don't exist or in other words it is a replica of "method missing" in dynamic languages.
Scalas 类型Dynamic允许您在不存在的对象上调用方法,或者换句话说,它是动态语言中“方法缺失”的复制品。
It is correct, scala.Dynamicdoesn't have any members, it is just a marker interface - the concrete implementation is filled-in by the compiler. As for Scalas String Interpolationfeature there are well defined rules describing the generated implementation. In fact, one can implement four different methods:
没错,scala.Dynamic没有任何成员,它只是一个标记接口——具体实现由编译器填写。至于 Scalas字符串插值功能,有明确定义的规则来描述生成的实现。事实上,可以实现四种不同的方法:
selectDynamic- allows to write field accessors:foo.barupdateDynamic- allows to write field updates:foo.bar = 0applyDynamic- allows to call methods with arguments:foo.bar(0)applyDynamicNamed- allows to call methods with named arguments:foo.bar(f = 0)
selectDynamic- 允许编写字段访问器:foo.barupdateDynamic- 允许写入字段更新:foo.bar = 0applyDynamic- 允许使用参数调用方法:foo.bar(0)applyDynamicNamed- 允许使用命名参数调用方法:foo.bar(f = 0)
To use one of these methods it is enough to write a class that extends Dynamicand to implement the methods there:
要使用这些方法之一,编写一个扩展类Dynamic并在那里实现这些方法就足够了:
class DynImpl extends Dynamic {
// method implementations here
}
Furthermore one need to add a
此外,还需要添加一个
import scala.language.dynamics
or set the compiler option -language:dynamicsbecause the feature is hidden by default.
或者设置编译器选项,-language:dynamics因为默认情况下该功能是隐藏的。
selectDynamic
选择动态
selectDynamicis the easiest one to implement. The compiler translates a call of foo.barto foo.selectDynamic("bar"), thus it is required that this method has an argument list expecting a String:
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
As one can see, it is also possible to call the dynamic methods explicitly.
如您所见,也可以显式调用动态方法。
updateDynamic
更新动态
Because updateDynamicis used to update a value this method needs to return Unit. Furthermore, the name of the field to update and its value are passed to different argument lists by the compiler:
因为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
The code works as expected - it is possible to add methods at runtime to the code. On the other side, the code isn't typesafe anymore and if a method is called that doesn't exist this must be handled at runtime as well. In addition this code is not as useful as in dynamic languages because it is not possible to create the methods that should be called at runtime. This means that we can't do something like
代码按预期工作 - 可以在运行时向代码添加方法。另一方面,代码不再是类型安全的,如果调用了不存在的方法,则也必须在运行时进行处理。此外,此代码不如动态语言有用,因为无法创建应在运行时调用的方法。这意味着我们不能做类似的事情
val name = "foo"
d.$name
where d.$namewould be transformed to d.fooat runtime. But this is not that bad because even in dynamic languages this is a dangerous feature.
whered.$name将d.foo在运行时转换为。但这并没有那么糟糕,因为即使在动态语言中,这也是一个危险的特性。
Another thing to note here, is that updateDynamicneeds to be implemented together with selectDynamic. If we don't do this we will get a compile error - this rule is similar to the implementation of a Setter, which only works if there is a Getter with the same name.
这里要注意的另一件事是,updateDynamic需要与selectDynamic. 如果我们不这样做,我们会得到一个编译错误——这个规则类似于 Setter 的实现,它只在有一个同名的 Getter 时才有效。
applyDynamic
应用动态
The ability to call methods with arguments is provided by 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
The name of the method and its arguments again are separated to different parameter lists. We can call arbitrary methods with an arbitrary number of arguments if we want but if we want to call a method without any parentheses we need to implement selectDynamic.
方法的名称及其参数再次被分隔到不同的参数列表。如果我们愿意,我们可以使用任意数量的参数调用任意方法,但是如果我们想调用一个没有任何括号的方法,我们需要实现selectDynamic。
Hint: It is also possible to use apply-syntax with applyDynamic:
提示:也可以将 apply-syntax 用于applyDynamic:
scala> d(5)
res1: String = method 'apply' called with arguments '5'
applyDynamicNamed
应用动态命名
The last available method allows us to name our arguments if we want:
如果需要,最后一个可用方法允许我们命名参数:
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)'
The difference in the method signature is that applyDynamicNamedexpects tuples of the form (String, A)where Ais an arbitrary type.
方法签名的不同之处在于applyDynamicNamed期望形式为(String, A)whereA是任意类型的元组。
All of the above methods have in common that their parameters can be parameterized:
以上所有方法的共同点是它们的参数都可以参数化:
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
Luckily, it is also possible to add implicit arguments - if we add a TypeTagcontext bound we can easily check the types of the arguments. And the best thing is that even the return type is correct - even though we had to add some casts.
幸运的是,还可以添加隐式参数 - 如果我们添加TypeTag上下文绑定,我们可以轻松检查参数的类型。最好的事情是即使返回类型也是正确的——即使我们不得不添加一些强制转换。
But Scala would not be Scala when there is no way to find a way around such flaws. In our case we can use type classes to avoid the casts:
但是当无法找到解决这些缺陷的方法时,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: _*)
}
}
While the implementation doesn't look that nice, its power can't be questioned:
虽然实现看起来并不那么好,但它的威力不容置疑:
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
At the top of all, it is also possible to combine Dynamicwith macros:
最重要的是,还可以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")
^
Macros give us back all compile time guarantees and while it is not that useful in the above case, maybe it can be very useful for some Scala DSLs.
宏为我们提供了所有编译时间保证,虽然在上述情况下它不是那么有用,但它可能对某些 Scala DSL 非常有用。
If you want to get even more information about Dynamicthere are some more resources:
如果您想获得更多信息,Dynamic这里有更多资源:
- The official SIP proposalthat introduced
Dynamicinto Scala - Practical uses of a Dynamic type in Scala- another question on SO (but very outdated)
- 引入Scala的官方SIP提案
Dynamic - Scala 中 Dynamic 类型的实际使用- 关于 SO 的另一个问题(但非常过时)

