eclipse-archived/ceylon

Proposal for Interface Delegation

Opened this issue · 4 comments

Interface delegation would be a great addition to Ceylon.
One of the drawbacks of composition over inheritance is boilerplate associated with implementing pass through methods of the interface a class wants to support.
I believe this could be mitigated if the language supported Interface Delegation.
The idea would be to have a keyword/annotation that allows for delegating parts of the implementing interface to child objects of a class

The statement would be in a constuctor of a class in form of:

delegate <query> to object

the query would have several forms

  • delegate all methods:
    • Interface::* or Interface
  • delegate some methods:
    • Interface::<methodname>,<methodname>...
  • any method defined in the class would take precedent over the delegation

example:

interface Shape{
	shared formal Number area();
	shared format String name();
}

interface Rectangle extends Shape{
	shared formal Number length();
	shared format Number width();
}

class StandardRectangle(Number length,Number width) implements Rectangle{
	shared actual Number area(){
		return length*width;
	}
	
	shared actual String name(){
	  return "Rectangle"
	}
}

class Square(length) implements Rectangle{
	value rectangle = StandardRectangle(length,length);
	delegate Rectangle::* to rectangle;
	//or 	delegate Rectangle::length,width,area to rectangle;
	//or 	delegate Rectangle to rectangle;

	shared actual String name(){
	  return "Square"
	}	
}

For that example code, you could do this (I replaced Number with Integer so it would compile):

interface Shape {
    shared formal Integer area;
    shared formal String name;
}

interface Rectangle satisfies Shape {
    shared formal Integer length;
    shared formal Integer width;
    
    shared default actual Integer area => length * width;
}

class Square(length) satisfies Rectangle {
    shared actual Integer length;
    
    width = length;
    
    shared actual String name => "Square";
}

Since you can use the default annotation for Rectangle.area, you don't need StandardRectangle.

@CPColin the point was to use composition, no?

drawbacks ... is boilerplate associated with implementing pass through methods of the interface a class wants to support

Yeah. But I guess a question is how bad is this in Ceylon, given the possibility of using Ceylon's abbreviated syntax? And, is doing something like Rectangle::* good, or would it always be better to list each member separately, to protect against future additions to the interface that you would want to review before simply delegating? I don't have a strong opinion on either.

For comparison, a streamlined implementation that's possible today:

interface Shape {
    shared formal Integer area();
    shared formal String name;
}

interface Rectangle satisfies Shape {
    shared formal Integer length;
    shared formal Integer width;
}

class StandardRectangle(
            shared actual Integer length,
            shared actual Integer width)
        satisfies Rectangle {
    area() => length * width;
    name => "Rectangle";
}

class Square(Integer l) satisfies Rectangle {
    value rectangle = StandardRectangle(l, l);

    // delegate to StandardRectangle
    length => rectangle.length;
    width => rectangle.width;
    area = rectangle.area;

    name => "Square";
}

The idea would be to make composition as easy as implementation inheritance.
The problem seems to be a common issue:
https://en.wikipedia.org/wiki/Composition_over_inheritance#Drawbacks
and other languages have come up with their solutiosn for it.
https://kotlinlang.org/docs/reference/delegation.html

If you had something like this I don't think you would need extend on class

guai commented

@marklester, kotlin is not a good example, it does not allow to delegate to its own property or some external object, but only to a constructor parameter which is very limiting
I'd also prefer not to describe what to include but what to exclude in delegation declaration. like lombok does