动态类型如何工作以及如何使用它? [英] How does type Dynamic work and how to use it?
问题描述
我听说使用 Dynamic
可以在 Scala 中进行动态输入.但我无法想象它会是什么样子或它是如何工作的.
I heard that with Dynamic
it is somehow possible to do dynamic typing in Scala. But I can't imagine how that might look like or how it works.
我发现可以从特征Dynamic
class DynImpl extends Dynamic
API 表示可以使用它像这样:
The API says that one can use it like this:
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")
^
这是完全合乎逻辑的,因为在查看 sources,原来这个特质完全是空的.没有定义方法 applyDynamic
,我无法想象如何自己实现它.
This is completely logical, because after looking to the sources, it turned out that this trait is completely empty. There is no method applyDynamic
defined and I can't imagine how to implement it by myself.
有人可以告诉我我需要做什么才能让它工作吗?
Can someone show me what I need to do to make it to work?
推荐答案
Scalas type Dynamic
允许你在不存在的对象上调用方法,或者换句话说,它是方法"的复制品缺少"在动态语言中.
Scalas type Dynamic
allows you to call methods on objects that don't exist or in other words it is a replica of "method missing" in dynamic languages.
正确,scala.Dynamic
没有任何成员,它只是一个标记接口——具体实现由编译器填充.至于 Scalas String Interpolation 特性,有定义明确的规则来描述生成的实现.实际上,可以实现四种不同的方法:
It is correct, scala.Dynamic
doesn't have any members, it is just a marker interface - the concrete implementation is filled-in by the compiler. As for Scalas String Interpolation feature there are well defined rules describing the generated implementation. In fact, one can implement four different methods:
selectDynamic
- 允许编写字段访问器:foo.bar
updateDynamic
- 允许写入字段更新:foo.bar = 0
applyDynamic
- 允许使用参数调用方法:foo.bar(0)
applyDynamicNamed
- 允许使用命名参数调用方法:foo.bar(f = 0)
selectDynamic
- allows to write field accessors:foo.bar
updateDynamic
- allows to write field updates:foo.bar = 0
applyDynamic
- allows to call methods with arguments:foo.bar(0)
applyDynamicNamed
- allows to call methods with named arguments:foo.bar(f = 0)
要使用这些方法之一,编写一个扩展 Dynamic
的类并在那里实现这些方法就足够了:
To use one of these methods it is enough to write a class that extends Dynamic
and to implement the methods there:
class DynImpl extends Dynamic {
// method implementations here
}
另外需要添加一个
import scala.language.dynamics
或者设置编译器选项-language:dynamics
,因为该功能默认是隐藏的.
or set the compiler option -language:dynamics
because the feature is hidden by default.
selectDynamic
是最容易实现的.编译器将foo.bar
的调用转换为foo.selectDynamic("bar")
,因此要求此方法有一个需要String 的参数列表代码>:
selectDynamic
is the easiest one to implement. The compiler translates a call of foo.bar
to foo.selectDynamic("bar")
, thus it is required that this method has an argument list expecting a 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
用于更新此方法需要返回Unit
的值.此外,要更新的字段名称及其值由编译器传递给不同的参数列表:
Because updateDynamic
is 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:
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
其中 d.$name
将在运行时转换为 d.foo
.但这并没有那么糟糕,因为即使在动态语言中,这也是一个危险的特性.
where d.$name
would be transformed to d.foo
at runtime. But this is not that bad because even in dynamic languages this is a dangerous feature.
这里要注意的另一件事是,updateDynamic
需要与 selectDynamic
一起实现.如果我们不这样做,我们会得到一个编译错误——这个规则类似于一个 Setter 的实现,它只在有一个同名的 Getter 时才有效.
Another thing to note here, is that updateDynamic
needs 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.
使用参数调用方法的能力由 applyDynamic
提供:
The ability to call methods with arguments is provided by 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
.
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
.
提示:也可以将 apply-syntax 与 applyDynamic
一起使用:
Hint: It is also possible to use apply-syntax with 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)'
方法签名的不同之处在于 applyDynamicNamed
需要 (String, A)
形式的元组,其中 A
是任意类型.
The difference in the method signature is that applyDynamicNamed
expects tuples of the form (String, A)
where A
is an arbitrary type.
以上所有方法的共同点是它们的参数都可以参数化:
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
幸运的是,也可以添加隐式参数 - 如果我们添加 TypeTag
上下文绑定我们可以很容易地检查参数的类型.最棒的是,即使返回类型也是正确的 - 即使我们不得不添加一些强制转换.
Luckily, it is also possible to add implicit arguments - if we add a TypeTag
context 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.
但是当无法找到解决这些缺陷的方法时,Scala 就不是 Scala.在我们的例子中,我们可以使用类型类来避免强制转换:
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:
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
最重要的是,还可以将 Dynamic
与宏结合起来:
At the top of all, it is also possible to combine Dynamic
with 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")
^
宏为我们提供了所有编译时间保证,虽然在上述情况下它不是那么有用,但它可能对某些 Scala DSL 非常有用.
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.
如果您想获得更多关于 Dynamic
的信息,还有更多资源:
If you want to get even more information about Dynamic
there are some more resources:
- 引入
Dynamic 的 官方 SIP 提案
进入 Scala - 动态类型在 Scala 中的实际使用 -关于 SO 的另一个问题(但非常过时)
- The official SIP proposal that introduced
Dynamic
into Scala - Practical uses of a Dynamic type in Scala - another question on SO (but very outdated)
这篇关于动态类型如何工作以及如何使用它?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!