动态类型如何工作以及如何使用它? [英] How does type Dynamic work and how to use it?

查看:31
本文介绍了动态类型如何工作以及如何使用它?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我听说使用 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:

  • 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆