ceylon icon indicating copy to clipboard operation
ceylon copied to clipboard

Proposal for Interface Delegation

Open marklester opened this issue 7 years ago • 4 comments
trafficstars

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

marklester avatar Oct 30 '18 04:10 marklester

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 avatar Oct 30 '18 16:10 CPColin

@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";
}

jvasileff avatar Oct 30 '18 17:10 jvasileff

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

marklester avatar Oct 30 '18 19:10 marklester

@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

guai avatar Feb 01 '19 17:02 guai