开发者

Proxies / delegates in Scala

I've seen several Scala questions recently (e.g. here, here, and here) that called for the use of proxies, and it's come up more than once in my own work. The Scala library has a number of proxy traits (14, if I counted correctly).

Proxy classes/traits usually contain lots of boilerplate:

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
}

My first thought was to define a Proxy[T] trait that could be used as follows:

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

where trait Proxy[T] extends T. Of course, 开发者_JAVA技巧it's not actually possible to define the Proxy trait without compiler magic.

My next thought was to look for a compiler plugin (such a capability clearly isn't in the existing compiler, or the sources for those 14 proxy traits would be much smaller). Sure enough, I found Kevin Wright's AutoProxy plugin. The plugin is intended to solve the proxy issue neatly, along with other use cases (including dynamic mixins):

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

Unfortunately, it looks like work on it stalled in November (2009). So, my questions are

  1. Is there continuing work on the AutoProxy plugin?
  2. Will this find its way into the compiler?
  3. Are any other approaches being considered?
  4. Finally, does this point to a significant weakness in Scala? After all, wouldn't it be possible to define a Proxy trait given lisp-style macros?


Four questions, four answers

  1. I am, though family has to come first! Plus others are involved in looking at the general issue with synthesizing methods in a compiler plugin.

  2. If so, it will most likely be in a different form, perhaps without using annotations.

  3. I don't know of any equivalent plugins, although one of the Scala GSOC candidate projects was based partly on my autoproxy code. There is, however, one very clean solution that will work in most cases and doesn't need a compiler plugin at all: You define an implicit conversion from FooProxy to Foo that simply returns the self member; this will get you most of the way there. The main issues with the approach are that it'll make life harder if you need to use your code from Java, it may be less efficient in terms of speed/memory, and it's another imlicit that you have to be aware of.

  4. The frustrating part is that almost all of the necessary logic is already available in the compiler, and it's used for mixins, so there really should be an elegant way of handling the task.


Adam Warski recently blogged about a Macro-based approach that may work in Scala 2.11 and definitely works with the Macro Paradise compiler plugin in Scala 2.10.

This library would allow you to write

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)
}

The project is in a very early, proof-of-concept, stage at the time of this writing, so caution is warranted.


For future reference, the library delegate-macro allows you to do just that.
It is cross compiled against 2.11, 2.12, and 2.13. For 2.11 and 2.12 you have to use the macro paradise compile plugin to make it work. For 2.13, you need to use flag -Ymacro-annotations instead.

Use it like this:

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)
}

It should work in most situations, including when type parameters and multiple argument lists are involved.

Disclaimer: I am the creator of the macro.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜