Feature Proposal - Automatic Delegation


#1

I would like to propose a new feature to the language - automatic delegation.

I very much favor composition over inheritance and therefore I create a lot of wrappers via the delegation design pattern, and in many of those wrappers I end up needing to alter the implementation of only a selected set of methods.

I would have liked to have a built-in feature in the language that saves me the time and effort of re-defining methods I don’t intend to change but only to delegate as is. Something like this:

trait Artist {
  def name: String
  def create(): Art
}

class Painter(override val name: String) extends Artist {
  override def create(): Art = ???
}

class ConArtist(inspiration: Artist) delegates Artist using inspiration {
  override val name: String = s"${inspiration.name} the original"
}

It is worth taking a look at a this stack-overflow question. The proposed solution there for the problem is quite interesting, but feels a little iffy and incomplete.

Thoughts?


#2

I think if wrappers do nothing except delegation it’s not good. It’s just reduce coupling . https://en.wikipedia.org/wiki/Coupling_(computer_programming).
But we also need delegation\proxy handlers for aspect oriented programming. Nowadays we use code generation for that purpose . It may be better to use macros.
I am interesting will it work:

trait Artist {
  def name: String
  def create(): Art
}

@Audit trait AuditArtist  extend Artist {
  @using 
  val inspiration: Artist
}

class ConArtist(inspiration: Artist)  with AuditArtist  

With white box macros we can generate method implementation in AuditArtist.
Will it be posible in dotty?


#3

I’m hesitant to propose it as I don’t think it’s hugely useful, but the easiest solution would be to use an implicit conversion.

implicit def toArtist(conArtist: ConArtist): Artist = conArtist.inspiration

But ultimately, I really don’t think the complexity of such a feature would be worth the relatively small benefit.


#4

How about def underscore

def _ = inspiration._

to mean generate the members, or if we extend a base class of the delegate, only the missing members.

def _ = _._

to mean generate missing members by delegating to possibly many delegates.

Since

implicit def _ = ???

will mean an implicit with a fresh name,

implicit def _ = inspiration._

will proxy only the implicit members, with fresh names.


#5

It never caught on but Adam Warski wrote an implementation of this a few years back with a blog post describing the implementation. http://www.warski.org/blog/2013/09/automatic-generation-of-delegate-methods-with-macro-annotations/


#6

Well obviously if a wrapper does nothing more than delegation then it shouldn’t exist in the first place. I’m talking about cases where some of the functionality is completely delegated, while some is altered.


#7

That’s the solution that was proposed in the linked SO question, but it’s not close to being a real delegator / wrapper / proxy. True though, it might very well be that this feature is too complex for its worth.


#8

What properties makes a delegator/wrapper/proxy “real”?


#9

I think it is a kind of dynamic dependency injection when:

  • we need to set trait’s implementation dynamically
  • we need be able to override some methods in this class

Nowadays we need to implement dynamic dependency injection manualy.

May be with trait parameters we will be able to do it more easily:

trait Artist {
  def name: String
  def create(): Art
}

@proxy trait PArtist(impl: Artist)  extend Artist

class ConArtist(inspiration: Artist)  with PArtist(inspiration)

I think it would be good ability


#10

Being able to decorate some of the methods instead of a full delegation (like the decorated name method in the original example).


#11

You can still do that through shadowing though.

C:\Users\Martijn>scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_102).
Type in expressions for evaluation. Or try :help.

scala> :paste
// Entering paste mode (ctrl-D to finish)

import scala.language.implicitConversions

class Art(val description: String) {
  override def toString() = s"about this work: $description"
}

trait Artist {
  def name: String
  def create(): Art
}

class Painter(override val name: String) extends Artist {
  override def create(): Art = new Art("a beautiful painting")
}

class ConArtist(val inspiration: Artist) {
  val name: String = s"${inspiration.name} the original"
}

object ConArtist {
  implicit def findInspiration(from: ConArtist): Artist = from.inspiration
}

val vermeer = new Painter("Johannes Vermeer")

val vanMeegeren = new ConArtist(vermeer)

// Exiting paste mode, now interpreting.

import scala.language.implicitConversions
defined class Art
defined trait Artist
defined class Painter
defined class ConArtist
defined object ConArtist
vermeer: Painter = Painter@6c4f9535
vanMeegeren: ConArtist = ConArtist@5bd1ceca

scala> vanMeegeren.name
res0: String = Johannes Vermeer the original

scala> vanMeegeren.create
res1: Art = about this work: a beautiful painting

scala>

So I guess I’m looking for a more rigourous definition of real delegator / wrapper / proxy. What code should compile/not compile for a real one, but not for an unreal one, or how would their observable behaviour differ. If their observable behaviour is the same, I suggest there isn’t actually a difference between a real one and an unreal one.

I’m not sure what decoration means, so maybe this lacks real decoration?


#12

I think that it does not work with java reflection. So such components will be difficult to use in frameworks which cooperate with components through reflection.


#13

I can’t believe people aren’t like, “def underscore!” like when they say SCORE! at World Cup matches. Probably the only way forward is to take propensive to an actual game of soccer or football or what have you, or, perhaps, foosball.


#14

Given that underscores are one of the more confusing things about Scala already, adding another meaning for them doesn’t seem like it would help that situation.

If this were to be a new language feature, you could have something like

class ConArtist(inspiration: Artist) extends inspiration {
  override def name: String = ???
}

which makes it pretty clear that it’s delegating to inspiration. OTOH, it further blurs the term/type lines by using a term in type position (I suppose it could be extends inspiration.type instead, but would imply that inspiration couldn’t be a var, which kills the dynamic use cases that I think the OP is talking about).

That said, I’ve wished for something like this on a handful of occasions, but given the language overhead I think it’s probably something better left to a compiler plugin. Which hopefully Scala 3 will still support?


#15

There are actually multiple design patterns closely related here - delegator, decorator, adapter and facade. But let’s not lose track, I’m interested in the following behavior:

def introduce(artist: Artist): String = s"${artist.name} belongs in a museum"

val painter = new Painter("Bansky")
val conArtist = new ConArtist(painter)

scala> introduce(conArtist)
res0: String = Bansky the original belongs in a museum

#16

Yes, for that you indeed need the feature you describe, or generate it through macros or code generation as far as I can see, though you still need subtyping, rather than composition, since def introduce(artist: Artist): String must be available through separate compilation, and scala won’t know that ConArtist must be able to be received by introduce when it compiles introduce.


#17

I used to like using this pattern when I wanted to expose a subset of the functionality of an object in other langs. Say I have a class that exposes 40 methods, I only want to expose 4 of them to my codebase. I could use a typeclass but when langs support first class forwarding it becomes very concise to do
class onlyTheGoodStuff
delegates(method1, method2, method3) to GoodStuffPlusAllKindsOfQuestionableStuff

It’s not as flexible as other solutions, you can’t easily rename methods with this style, but it’s so cheap and easy I found it quite useful. Haven’t heard a name for it but it’s almost an undecorator pattern, don’t add stuff, take it away.


#18

It’s called a Kondo in the literature, after Marie Kondo. To declutter an interface, ask yourself which methods make you happy, and throw out the other stuff. I’m going to start using undecorator. If you do it and break compatibility, it’s called the indecorous pattern.


#19

I think when we write library or facade it is no matter how we must do it.
I can not see a big deffrence between

class onlyTheGoodStuff
delegates(method1, method2, method3) to GoodStuffPlusAllKindsOfQuestionableStuff

or

class SomeClass ... {
  def method():Unit  = someImpl.method
}

It has very little influence in final costs.

But in everyday tasks when we create a special business logic, for example components for services or user forms, or tests etc.
It’s very important to have comfortable way to add functionality.
For example ( http://www.scalatest.org/quick_start )

class ExampleSpec extends FlatSpec with Matchers {

It is ridiculous to create delegates manually in such case :slight_smile:


#20

I didn’t see it mentioned anywhere on this thread, but this was already discussed as part of the discussion around adding an export keyword (delegation was one of the proposed semantics).

Also, I’m not in favor of a syntax that requires the object being delegated to to be a parameter of the class, which I find too restrictive. Why not allow delegation to any fields?