【问题标题】:Proxies / delegates in ScalaScala 中的代理/委托
【发布时间】:2011-03-29 05:58:04
【问题描述】:

我最近看到了几个需要使用代理的 Scala 问题(例如 hereherehere),并且在我自己的工作中不止一次出现过。 Scala 库有许多代理特征(如果我没记错的话,有 14 个)。

代理类/特征通常包含很多样板:

class FooProxy(val self: Foo) extends Foo {
   // added behavior
   def mymethod = ...

   // forwarding methods
   def method1 = self.method1
   def method2(arg: String) = self.method2(arg)
   ...
}

trait Foo {
   def method1: Unit
   def method2(arg: String): Unit
}

我的第一个想法是定义一个Proxy[T] trait,可以如下使用:

class FooProxy(val self: Foo) extends Proxy[Foo] {
   // added behavior
   def mymethod = ...
}

在哪里trait Proxy[T] extends T。当然,实际上不可能在没有编译器魔法的情况下定义 Proxy 特征。

我的下一个想法是寻找一个编译器插件(这样的功能显然不在现有的编译器中,或者这 14 个代理特征的来源会小得多)。果然,我找到了Kevin Wright's AutoProxy plugin。该插件旨在巧妙地解决代理问题,以及其他用例(包括动态混合):

class FooProxy(@proxy val self: Foo) { ... }

不幸的是,它的工作似乎在 11 月(2009 年)停滞不前。所以,我的问题是

  1. 是否有关于 AutoProxy 插件的继续工作?
  2. 这会进入编译器吗?
  3. 是否正在考虑其他方法?
  4. 最后,这是否表明 Scala 存在显着的弱点?毕竟,给定 lisp 风格的宏难道不能定义 Proxy 特征吗?

【问题讨论】:

  • 特征不能有参数。您是否建议添加它们?此外,您还没有展示任何无法通过添加隐式转换来修复的内容。创建隐式转换的提议是不必要的样板吗?
  • “特征不能有参数”:愚蠢的错误,已修复。
  • 隐式转换解决了类似的问题,但它们并不总是合适的(否则为什么 EPFL 的家伙会在 Scala 库中包含这么多代理?)。一方面,它们比委派产生更多的开销。其次,大量使用隐式转换会损害可维护性/可读性。
  • 最后,除非有人能想到一个聪明的解决方法,否则这可能是杀手锏,隐式转换不会保留修改后的行为。也就是说,如果代理覆盖了某个方法,则该覆盖在转换​​时会丢失。因此,如果我将FooProxy 放入List[Foo],它的酷炫装饰就会突然消失。

标签: scala delegates scala-2.8


【解决方案1】:

四个问题,四个答案

  1. 我是,但家庭必须放在第一位!此外,其他人也参与了在编译器插件中合成方法的一般问题的研究。

  2. 如果是这样,它很可能是不同的形式,可能没有使用注释。

  3. 我不知道任何等效插件,尽管 Scala GSOC 候选项目之一部分基于我的自动代理代码。然而,有一个非常干净的解决方案可以在大多数情况下工作并且根本不需要编译器插件:您定义从 FooProxy 到 Foo 的隐式转换,它只返回 self 成员;这将使您大部分时间到达那里。该方法的主要问题是,如果您需要使用 Java 代码,它会使生活变得更加困难,它在速度/内存方面的效率可能较低,这是您必须注意的另一个隐含因素。

  4. 令人沮丧的部分是编译器中几乎所有必要的逻辑都已经可用,并且它用于混合,因此确实应该是一种处理任务的优雅方式。

【讨论】:

【解决方案2】:

Adam Warski recently blogged 关于 a Macro-based approach 可能在 Scala 2.11 中工作,并且绝对可以在 Scala 2.10 中使用 Macro Paradise 编译器插件。

这个库可以让你写

class FooProxy(@delegate wrapped: Foo) extends Foo {
    // added behavior
    def mymethod = ...

    // forwarding methods (generated for you)
    // def method1 = wrapped.method1
    // def method2(arg: String) = wrapped.method2(arg)
}

在撰写本文时,该项目处于非常早期的概念验证阶段,因此需要谨慎。

【讨论】:

    【解决方案3】:

    为了将来参考,库 delegate-macro 允许您这样做。
    它与2.112.122.13 交叉编译。对于2.112.12,您必须使用宏天堂编译插件才能使其工作。对于2.13,您需要使用标志-Ymacro-annotations

    像这样使用它:

    trait Connection {
      def method1(a: String): String
      def method2(a: String): String
      // 96 other abstract methods
      def method100(a: String): String
    }
    
    @Delegate
    class MyConnection(delegatee: Connection) extends Connection {
      def method10(a: String): String = "Only method I want to implement manually"
    }
    
    // The source code above would be equivalent, after the macro expansion, to the code below
    class MyConnection(delegatee: Connection) extends Connection {
      def method1(a: String): String = delegatee.method1(a)
      def method2(a: String): String = delegatee.method2(a)
      def method10(a: String): String = "Only method I need to implement manually"
      // 96 other methods that are proxied to the dependency delegatee
      def method100(a: String): String = delegatee.method100(a)
    }
    

    它应该适用于大多数情况,包括涉及类型参数和多个参数列表时。

    免责声明:我是宏的创建者。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多