斯卡拉动力学 : Ability to add dynamic methods?

标签 scala dynamic reflection scala-2.10 scala-macros

我知道我可以像这样添加动态“字段”:

import collection.mutable

class DynamicType extends Dynamic {

  private val fields = mutable.Map.empty[String, Any].withDefault {key => throw new NoSuchFieldError(key)}

  def selectDynamic(key: String) = fields(key)

  def updateDynamic(key: String)(value: Any) = fields(key) = value

  def applyDynamic(key: String)(args: Any*) = fields(key)
}

然后我可以做这样的事情:

val foo = new DynamicType
foo.age = 23
foo.name = "Rick"

但是,我想进一步扩展这一步骤并添加动态方法,例如:

foo.greet = (name: String) => s"Nice to meet you $name, my name is ${this.name}"

foo.greet("Nat"); //should return "Nice to meet you Nat, my name is Rick"

我尝试将所有方法存储在 updateDynamic 的单独映射中,但我无法找到处理 arity problem 的通用方法.那么有没有办法使用 Macros + Dynamics 来做这样的事情呢?

编辑: 根据@Petr Pudlak 的回答,我尝试实现这样的东西:

import collection.mutable
import DynamicType._

/**
 * An useful dynamic type that let's you add/delete fields and methods during runtime to a structure
 */
class DynamicType extends Dynamic {

  private val fields = mutable.Map.empty[String, Any] withDefault { key => throw new NoSuchFieldError(key) }
  private val methods = mutable.Map.empty[String, GenFn] withDefault { key => throw new NoSuchMethodError(key) }

  def selectDynamic(key: String) = fields(key)

  def updateDynamic(key: String)(value: Any) = value match {
    case fn0: Function0[Any] => methods(key) = {case Seq() => fn0()}
    case fn1: Function1[Any, Any] => methods(key) = fn1
    case fn2: Function2[Any, Any, Any] => methods(key) = fn2
    case _ => fields(key) = value
  }

  def applyDynamic(key: String)(args: Any*) = methods(key)(args)

  /**
   * Deletes a field (methods are fields too)
   * @return the old field value
   */
  def delete(key: String) = fields.remove(key)

  //todo: export/print to json
}

object DynamicType {
  import reflect.ClassTag

  type GenFn = PartialFunction[Seq[Any],Any]
  implicit def toGenFn1[A: ClassTag](f: (A) => Any): GenFn = { case Seq(a: A) => f(a) }
  implicit def toGenFn2[A: ClassTag, B: ClassTag](f: (A, B) => Any): GenFn = { case Seq(a: A, b: B) => f(a, b) }
  // todo: generalize to 22-args
}

Full code here

1) 它可以正确处理字段与方法(甚至是 0-args),但非常冗长(目前最多只能使用 2 个 arg 方法)。有没有办法简化我的代码?

2) 无论如何支持动态方法重载(例如,添加 2 个具有不同签名的动态方法?)如果我可以获得函数的签名,我可以将其用作 methods 中的键> map 。

最佳答案

为了做到这一点,我们必须解决两个问题:

  1. 以某种方式将所有可能的函数统一为一种数据类型。
  2. 处理动态select既可以返回值又可以返回函数,我们无法提前确定。

这是一种可能性:

首先,让我们定义最通用的函数类型:获取任意数量的任意参数并产生结果,如果参数的数量或类型不匹配,则失败:

type GenFn = PartialFunction[Seq[Any],Any]

现在我们创建一个动态类型,其中一切都是 GenFn:

class DynamicType extends Dynamic {
  import collection.mutable

  private val fields =
    mutable.Map.empty[String,GenFn]
      .withDefault{ key => throw new NoSuchFieldError(key) }

  def selectDynamic(key: String) = fields(key)
  def updateDynamic(key: String)(value: GenFn) = fields(key) = value
  def applyDynamic(key: String)(args: Any*) = fields(key)(args);
}

接下来,让我们创建隐式转换,将不同数量的函数转换为这种类型:

import scala.reflect.ClassTag
implicit def toGenFn0(f: => Any): GenFn =
  { case Seq() => f; }
implicit def toGenFn1[A: ClassTag](f: (A) => Any): GenFn =
  { case Seq(x1: A) => f(x1); }
implicit def toGenFn2[A: ClassTag,B: ClassTag](f: (A,B) => Any): GenFn =
  { case Seq(x1: A, x2: B) => f(x1, x2); }
// ... other arities ...

每次转换都会将一个函数(或一个值)转换为一个 GenFn - 一个部分函数,​​如果给定的参数数量/类型错误,则该函数会失败。 我们使用 ClassTag 以便能够匹配正确类型的参数。请注意,我们将值视为零参数的函数。这样我们处理 2. 的代价是通过在 name() 中给出零参数来使用检索值。

最后,我们可以这样做:

val foo = new DynamicType
foo.name = "Rick"
foo.greet = (name: String) =>
    s"Nice to meet you $name, my name is ${foo.name()}"
println(foo.greet("Nat"));

为了支持方法重载,我们只需要链接PartialFunctions。这可以实现为

  def updateDynamic(key: String)(value: GenFn) =
    fields.get(key) match {
      case None     => fields(key) = value
      case Some(f)  => fields(key) = f.orElse(value);
    }

(请注意,它不是线程安全的)。然后我们可以调用类似的东西

val foo = new DynamicType
foo.name = "Rick"
foo.greet = (name: String)
              => s"Nice to meet you $name, my name is ${foo.name()}"
foo.greet = (firstName: String, surname: String)
              => s"Nice to meet you $firstName $surname, my name is ${foo.name()}"
println(foo.greet("Nat"));
println(foo.greet("Nat Smith"));

请注意,此解决方案的工作方式与标准方法重载略有不同。这取决于添加功能的顺序。如果先添加更通用的函数,则永远不会调用更具体的函数。所以总是先添加更具体的功能。

你这样做可能会更困难,因为你似乎不区分函数的类型(就像我的 toGenFn... 方法一样),所以如果函数得到错误的参数,它只会抛出一个异常而不是将它们传递给下一行。但它应该适用于具有不同数量参数的函数。

我认为不可能避免检查各种参数的函数所导致的冗长,但我认为这并不重要。这只是一次性工作,DynamicType的客户端不受它的影响。

关于斯卡拉动力学 : Ability to add dynamic methods?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18557183/

相关文章:

asp.net-mvc - 如何从数据库动态加载aspx代码?

java - Class#isAnnotationPresent 不起作用

ios - 有没有办法让 NSInvocation 支持变量 parmas 函数行 [NSstring stringWithFormat :. .]

c++ - 类中类的动态数组

xml - 我是否发现了 AS3 的 XML 类中的错误?

swift - 是否有与 C#'s ' nameof( )' function to get a variable or member' s name?

scala - 在范围内找不到 Spark 隐式编码器

scala - 如何在它们自己的执行上下文中而不是在 Actor 系统调度程序上运行 future。 [斯卡拉| Akka ]

scala - 如何覆盖控制台的 scalacOptions?

list - 如何在 Scala 中保持表达式的函数式风格