如何键入工作动态,以及如何使用它?(How does type Dynamic work and h

2019-08-19 08:49发布

我听说有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定义,我无法想象如何通过自己实现它。

可有人告诉我,我需要做的,它的工作?

Answer 1:

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.barfoo.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另一个问题(但非常过时)


文章来源: How does type Dynamic work and how to use it?
标签: scala