[swift-evolution] [pitch] Implementation composition
Jay Abbott
jay at abbott.me.uk
Fri Nov 25 09:33:52 CST 2016
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?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161125/c07031d0/attachment.html>
More information about the swift-evolution
mailing list