[pitch] Implementation composition


(Jay) #1

We already have a great way to compose APIs using protocol composition, and
we can supply default implementations for protocol methods, but what if we
want to compose implementations from existing types?

Say I have two disparate protocols:

protocol A {
    func methodA1()
    func methodA2()
}protocol B {
    func methodB1()
    func methodB2()
}

And I also have a selection of classes that implement them, but let’s just
consider two:

class ImplementsA : A {
    func methodA1() {
        print("A1")
    }
    func methodA2() {
        print("A2")
    }
}class ImplementsB : B {
    func methodB1() {
        print("B1")
    }
    func methodB2() {
        print("B2")
    }
}

And I have a composed interface:

typealias Useful = A & B

Now I want to implement a Useful class by composing it from the two chosen
implementations of A and B:

class MyClass : Useful {
    private let a = ImplementsA()
    private let b = ImplementsB()

    public func methodA1() {
        a.methodA1()
    }
    public func methodA2() {
        a.methodA2()
    }
    public func methodB1() {
        b.methodB1()
    }
    public func methodB2() {
        b.methodB2()
        // I want this to do what 'b' does, plus some
        // extra work in MyClass implementations of B
        print("Extra")
    }
}

Not too bad - but that could get pretty tedious if I had 5 protocols to
implement with 5 methods each. Much nicer would be:

class MyClass : Useful {
    private let a = ImplementsA() implements A
    private let b = ImplementsB() implements B

    public func methodB2() {
        b.methodB2()
        // I want this to do whatever 'b' does, plus some
        // extra work in MyClass implementations of B
        print("Extra")
    }
}

The idea is that implements SomeProtocol after a member variable will
synthesize all the methods that aren’t explicitly implemented from
SomeProtcol by forwarding the call to that member. Or something more
efficient if possible.

You could also implement protocols using other classes that only partially
implement them:

class PartlyImplementsB {
    func methodB1() {
        print("B1")
    }
}class MyClass : Useful {
    private let a = ImplementsA() implements A
    private let b = PartlyImplementsB() implements B

    public func methodB2() {
        print("I have to implement this because `b` does not.")
    }
}

The way this would work is find the intersection between all methods in the
protocol and all methods in the implementing member, then subtract all
methods already explicitly implemented in the class, and synthesize those.
That way if you had another class AlmostImplementsB that implements methodB2
you could simply do:

    private let a = ImplementsA() implements A
    private let b1 = PartlyImplementsB() implements B
    private let b2 = AlmostImplementsB() implements B

However, if the synthesis process finds that it’s synthesizing a method
twice, for example in this case…

protocol C {
    func methodC1()
    func methodC2()
    func methodC3()
}class PartlyImplementsC {
    func methodC1() {
        print("C1(partly)")
    }
    func methodC2() {
        print("C2(partly)")
    }
}class AlmostImplementsC {
    func methodC2() {
        print("C2(almost)")
    }
    func methodC3() {
        print("C3(almost)")
    }
}class MyClass : C {
    private let cPartly = PartlyImplementsC() implements C
    private let cAlmost = AlmostImplementsC() implements C
}

…then the compiler would emit an error and you would have to explicitly
implement methodC2 to prevent it from being double-synthesized. You could
of course have your own custom implementation or choose which member to
call as your explicit implementation.

Regarding access: I think it would implement them as public, as this seems
obvious for a protocol, but I guess there’s a possibility you might want
them to be internal, so perhaps implements(internal) or implements(public)
would be better. Or perhaps someone can think of a better word because in
the partial case it is a little confusing - is there a single word that
means use-to-implement ?

Regarding value-types: I haven’t thought deeply about this for non-class
types, but it can probably work the same for those too.

Anyway, this could be used to provide a variety of implementations for
protocols, composed of different combinations of partial implementations,
then use those complete implementations to compose your larger/complex
types with the minimum of boilerplate forwarding code.

Thoughts?


(Rien) #2

I like that.

Have you considered the following?

protocol foobar {
  func foo() …
  func bar() ...
}

class A: foobar { …}

class B: foobar {
  let a = A() implements foobar.foo
  let b = A() implements foobar.bar
}

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Swiftrien
Project: http://swiftfire.nl

···

On 25 Nov 2016, at 16:33, Jay Abbott via swift-evolution <swift-evolution@swift.org> wrote:

We already have a great way to compose APIs using protocol composition, and we can supply default implementations for protocol methods, but what if we want to compose implementations from existing types?

Say I have two disparate protocols:

protocol A
{
    
func methodA1()

func methodA2()

}

protocol B
{
    
func methodB1()

func methodB2()

}

And I also have a selection of classes that implement them, but let’s just consider two:

class ImplementsA : A
{
    
func methodA1()
{
        
print("A1"
)
    }
    
func methodA2()
{
        
print("A2"
)
    }
}

class ImplementsB : B
{
    
func methodB1()
{
        
print("B1"
)
    }
    
func methodB2()
{
        
print("B2"
)
    }
}

And I have a composed interface:

typealias Useful = A & B
Now I want to implement a Useful class by composing it from the two chosen implementations of A and B:

class MyClass : Useful
{
    private
let a = ImplementsA
()
    private
let b = ImplementsB
()

    public
func methodA1()
{
        a.methodA1()
    }
    public
func methodA2()
{
        a.methodA2()
    }
    public
func methodB1()
{
        b.methodB1()
    }
    public
func methodB2()
{
        b.methodB2()
        
// I want this to do what 'b' does, plus some

// extra work in MyClass implementations of B

print("Extra"
)
    }
}

Not too bad - but that could get pretty tedious if I had 5 protocols to implement with 5 methods each. Much nicer would be:

class MyClass : Useful
{
    private
let a = ImplementsA() implements A

    private
let b = ImplementsB() implements B

    public
func methodB2()
{
        b.methodB2()
        
// I want this to do whatever 'b' does, plus some

// extra work in MyClass implementations of B

print("Extra"
)
    }
}

The idea is that implements SomeProtocol after a member variable will synthesize all the methods that aren’t explicitly implemented from SomeProtcol by forwarding the call to that member. Or something more efficient if possible.

You could also implement protocols using other classes that only partially implement them:

class PartlyImplementsB
{
    
func methodB1()
{
        
print("B1"
)
    }
}

class MyClass : Useful
{
    private
let a = ImplementsA() implements A

    private
let b = PartlyImplementsB() implements B

    public
func methodB2()
{
        
print("I have to implement this because `b` does not."
)
    }
}

The way this would work is find the intersection between all methods in the protocol and all methods in the implementing member, then subtract all methods already explicitly implemented in the class, and synthesize those. That way if you had another class AlmostImplementsB that implements methodB2 you could simply do:

    private let a = ImplementsA() implements A

    private
let b1 = PartlyImplementsB() implements B

    private
let b2 = AlmostImplementsB() implements B
However, if the synthesis process finds that it’s synthesizing a method twice, for example in this case…

protocol C
{
    
func methodC1()

func methodC2()

func methodC3()

}

class PartlyImplementsC
{
    
func methodC1()
{
        
print("C1(partly)"
)
    }
    
func methodC2()
{
        
print("C2(partly)"
)
    }
}

class AlmostImplementsC
{
    
func methodC2()
{
        
print("C2(almost)"
)
    }
    
func methodC3()
{
        
print("C3(almost)"
)
    }
}

class MyClass : C
{
    private
let cPartly = PartlyImplementsC() implements C

    private
let cAlmost = AlmostImplementsC() implements C

}

…then the compiler would emit an error and you would have to explicitly implement methodC2 to prevent it from being double-synthesized. You could of course have your own custom implementation or choose which member to call as your explicit implementation.

Regarding access: I think it would implement them as public, as this seems obvious for a protocol, but I guess there’s a possibility you might want them to be internal, so perhaps implements(internal) or implements(public) would be better. Or perhaps someone can think of a better word because in the partial case it is a little confusing - is there a single word that means use-to-implement ?

Regarding value-types: I haven’t thought deeply about this for non-class types, but it can probably work the same for those too.

Anyway, this could be used to provide a variety of implementations for protocols, composed of different combinations of partial implementations, then use those complete implementations to compose your larger/complex types with the minimum of boilerplate forwarding code.

Thoughts?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tino) #3

Kotlin has a imho really nice solution for this: Because constructor parameters appear right after the class declaration, you can refer to them in the inheritance-clause.

Protocol conformance is a quite important aspect of a type, so I'm not sure if it wise to allow "hiding" it — and I hope the promised macro-system will allow forwarding, so that there is no special syntax needed.

Bottom line:
Imho it's not the right time for serious discussion (serious means: with the goal of a accepted proposal :wink:


(Jay) #4

Tino,

In what way is this “hiding” protocol conformance? In the examples, MyClass
is declaring conformance to A and B via the Useful protocol composition.
Perhaps I should have made it clearer which bits are new/proposed: Only the
implements word is new here, the protocol composition syntax (A & B)
already exists.

You’re probably right that this is an additive (4.1) thing. But personally
I see no harm in building up a backlog of useful proposals to be reviewed
next year. The knowledge of what’s to come may also help influence thinking
on what’s to come next. Should such discussions be flagged as additive in
the subject somehow? I think I asked this before but forgot or missed if
there was a response.

···

On Fri, 25 Nov 2016 at 16:21 Tino Heth <2th@gmx.de> wrote:

Kotlin has a imho really nice solution for this: Because constructor
parameters appear right after the class declaration, you can refer to them
in the inheritance-clause.

Protocol conformance is a quite important aspect of a type, so I'm not sure
if it wise to allow "hiding" it — and I hope the promised macro-system will
allow forwarding, so that there is no special syntax needed.

Bottom line:
Imho it's not the right time for serious discussion (serious means: with
the goal of a accepted proposal :wink:


(Tino) #5

In what way is this “hiding” protocol conformance? In the examples, MyClass is declaring conformance to A and B via the Useful protocol composition. Perhaps I should have made it clearer which bits are new/proposed: Only the implements word is new here, the protocol composition syntax (A & B) already exists.

My fault — but imho this "sub-protocol" situation isn't that common, and it would make the proposal simpler if it starts with just forwarding:
class MyClass : A {
    private let a = ImplementsA() implements A
...

In this case, there is (small) repetition (which imho isn't that bad — but Kotlin has a nicer syntax for it).


#6

Protocol forwarding was discussed on-list near the end of 2015 in the
thread “[swift-evolution] [Proposal Draft] automatic protocol forwarding”.
Feedback was generally positive, though there were disagreements about
whether the forwarder and/or forwardee should have to conform to the
protocol. Also, complications regarding Self requirements were brought up.

For simplicity, if we are going to do this, I propose the following:

*Direct forwarding*
• Both the forwarder and the forwardee must conform to the protocol being
forwarded.
• The forwarder’s associated types are inferred to (and must) match the
forwardee’s.
• Requirements the forwarder implements take precedence and are not
forwarded.

*Self requirements*
• A return type of Self cannot be forwarded.
• A return type of, eg. Self.Element, *can* be forwarded, since the
associated types match.
• A parameter type of Self is forwarded in the natural way, by calling the
forwardee’s implementation with the argument’s forwardee.

Example of that last point:

protocol P { func f(_: Self) }
struct Q: P { func f(_: Q){ } }
class C: P {
  var q: Q implements P

  // synthesized:
  func f(_ a: C) {
    q.f(a.q)
  }
}

Some of these restrictions could potentially be lifted in the future,
however for the time being I think they serve an important purpose.

All that said, I am not yet entirely convinced that protocol forwarding
carries sufficient benefits to justify its addition to the language.

Nevin


(Matthew Johnson) #7

Protocol forwarding was discussed on-list near the end of 2015 in the thread “[swift-evolution] [Proposal Draft] automatic protocol forwarding”. Feedback was generally positive, though there were disagreements about whether the forwarder and/or forwardee should have to conform to the protocol. Also, complications regarding Self requirements were brought up.

I was the author of the draft proposal discussed in that thread. I’m sitting on a second draft which is about 2/3 complete. I will finish the draft and bring it back up when the core team is ready to consider proposals along these lines again.

The ideas I have run in a different and more general direction than what you suggest below. But let’s wait until the time is right to dive into the details.

···

On Nov 25, 2016, at 12:06 PM, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

For simplicity, if we are going to do this, I propose the following:

Direct forwarding
• Both the forwarder and the forwardee must conform to the protocol being forwarded.
• The forwarder’s associated types are inferred to (and must) match the forwardee’s.
• Requirements the forwarder implements take precedence and are not forwarded.

Self requirements
• A return type of Self cannot be forwarded.
• A return type of, eg. Self.Element, *can* be forwarded, since the associated types match.
• A parameter type of Self is forwarded in the natural way, by calling the forwardee’s implementation with the argument’s forwardee.

Example of that last point:

protocol P { func f(_: Self) }
struct Q: P { func f(_: Q){ } }
class C: P {
  var q: Q implements P
  
  // synthesized:
  func f(_ a: C) {
    q.f(a.q)
  }
}

Some of these restrictions could potentially be lifted in the future, however for the time being I think they serve an important purpose.

All that said, I am not yet entirely convinced that protocol forwarding carries sufficient benefits to justify its addition to the language.

Nevin

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution