Scala 类型动态如何工作以及如何使用它?

Scala 类型动态如何工作以及如何使用它?,scala,Scala,我听说使用Dynamic可以在Scala中进行动态键入。但我无法想象这会是什么样子,或者它是如何工作的 我发现一个人可以继承traitDynamic class DynImpl extends Dynamic 据说人们可以这样使用它: foo.method(“blah”)~~~>foo.applyDynamic(“method”)(“blah”) 但当我尝试它时,它不起作用: scala> (new DynImpl).method("blah") <console>:17:

我听说使用
Dynamic
可以在Scala中进行动态键入。但我无法想象这会是什么样子,或者它是如何工作的

我发现一个人可以继承trait
Dynamic

class DynImpl extends Dynamic
据说人们可以这样使用它:

foo.method(“blah”)~~~>foo.applyDynamic(“method”)(“blah”)

但当我尝试它时,它不起作用:

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")
               ^
scala>(新的DynImpl.method(“blah”)
:17:错误:值applyDynamic不是DynImpl的成员
重写到new DynImpl()后出错,结果表明此特征完全为空。没有定义任何方法
applyDynamic
,我无法想象自己如何实现它


有人能告诉我需要做什么才能让它工作吗?

Scalas type
Dynamic
允许您在不存在的对象上调用方法,或者换句话说,它是动态语言中“method missing”的复制品

它是正确的,没有任何成员,它只是一个标记接口-具体实现由编译器填充。至于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
,因为默认情况下该功能是隐藏的

选择动态
选择dynamic
是最容易实现的方法。编译器将对
foo.bar
的调用转换为
foo.selectDynamic(“bar”)
,因此要求此方法具有一个参数列表,该参数列表应为
字符串

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
用于更新值,因此此方法需要返回
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
一起实现。如果我们不这样做,我们将得到一个编译错误-这个规则类似于Setter的实现,它只在有同名Getter的情况下才有效

应用动力学
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
scala> d(5)
res1: String = method 'apply' called with arguments '5'
ApplyDynamicName 如果需要,最后一个可用的方法允许我们命名参数:

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
幸运的是,还可以添加隐式参数——如果我们添加上下文绑定,我们可以轻松地检查参数的类型。最好的是,即使返回类型是正确的——尽管我们必须添加一些类型转换

但如果无法找到解决这些缺陷的方法,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: _*)
  }

}
虽然实现看起来不太好,但其威力不容质疑:

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")
                       ^
类DynImpl扩展了动态{
导入language.experimental.macros
def applyDynamic[A](名称:字符串)(args:A*):A=macro DynImpl.applyDynamic[A]
}
对象DynImpl{
导入reflect.macros.Context
进口DynTypes_
def applyDynamic[A:c.WeakTypeTag](c:Context)(名称:c.Expr[String])(args:c.Expr[A]*)={
导入c.universe_
val Literal(常量(defName:String))=name.tree
val res=defName匹配{
如果weakTypeOf[A]=:=weakTypeOf[Int]=>
val-seq=args-map(u.tree)map{case-Literal(常量(c:Int))=>c}
隐式[DynType[Int]].exec(seq:*)
如果weakTypeOf[A]=:=weakTypeOf[String]=>
val-seq=args-map(u.tree)map{case-Literal(常量(c:String))=>c}
隐式[DynType[String]].exec(seq:*)
案例=>
val-seq=args-map(u.tree)map{case-Literal(常量(c))=>c}
c、 中止(c.EnclosuringPosition,s“带有参数${seq.mkString(“'”,“',“'”,“'”,“')}的方法'$defName'不存在”)
}
c、 Expr(文本(常量(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”)
:11:错误:带有参数“a”、“b”、“c”的方法“noexist”不存在
d、 不存在(“a”、“b”、“c”)
^

宏给了我们所有的编译时保证,虽然它在上述情况下没有那么有用,但对于某些Sc可能非常有用