> On Dec 29, 2015, at 5:25 PM, Charles Srstka <cocoa...@charlessoft.com> wrote:
> 
> Strong +1 on this proposal. I use Objective-C’s forwarding mechanisms quite 
> often in my custom view code, in order to separate the code managing the 
> outer view, the layout of subviews within the view, and business logic into 
> separate classes, all while presenting a single, monolithic interface to the 
> user. The loss of this ability without writing tons of boilerplate is one of 
> the things about Swift that makes me sad.
> 
> The one thing I’d change is upgrading the partial forwarding synthesis to the 
> original proposal, as that’s a rather important feature IMO.
> 

Thanks Charles.  Do you disagree with the reasons I decided not to include 
partial forwarding in the initial proposal (it is discussed in alternatives 
considered)?


> Charles
> 
>> On Dec 29, 2015, at 10:37 AM, Matthew Johnson via swift-evolution 
>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>> 
>> I have completed a first draft of a proposal to introduce automatic protocol 
>> forwarding.  I’m looking forward to feedback from everyone!
>> Automatic protocol forwarding
>> 
>> Proposal: SE-NNNN 
>> <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-automatic-protocol-forwarding.md>
>> Author(s): Matthew Johnson <https://github.com/anandabits>
>> Status: Review
>> Review manager: TBD
>> Introduction
>> 
>> Automatic protocol forwarding introduces the ability to use delegation 
>> without the need write forwarding member implementations manually. 
>> 
>> A preliminary mailing list thread on this topic had the subject protocol 
>> based invocation forwarding 
>> <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/000931.html>
>> Motivation
>> 
>> Delegation is a robust, composition oriented design technique that keeps 
>> interface and implementation inheritance separate. The primary drawback to 
>> this technique is that it requires a lot of manual boilerplate to forward 
>> implemenation to the implementing member. This proposal eliminates the need 
>> to write such boilerplate manually, thus making delegation-based designs 
>> much more convenient and attractive.
>> 
>> This proposal may also serve as the foundation for a future enhancement 
>> allowing a very concise “newtype” declaration.
>> 
>> Proposed solution
>> 
>> I propose introducing a forward declaration be allowed within a type 
>> declaration or type extension. The forward declaration will cause the 
>> compiler to synthesize implementations of the members required by the 
>> forwarded protocols. The synthesized implementations will simply forward the 
>> method call to the specified member.
>> 
>> The basic declaration looks like this:
>> 
>> forward Protocol, OtherProtocol to memberIdentifier
>> The first clause contains a list of protocols to forward. 
>> 
>> The second clause specifies the identifier of the property to which the 
>> protocol members will be forwarded. Any visible property that implements the 
>> members required by the protocol is eligible for forwarding. It does not 
>> matter whether it is stored, computed, lazy, etc.
>> 
>> It is also possible to include an access control declaration modifier to 
>> specify the visibility of the synthesized members.
>> 
>> Self parameters
>> 
>> When a protocol member includes a Self parameter forwarding implementations 
>> must accept the forwarding type but supply an argument of the forwardee type 
>> when making the forwarding call. The most straightforward way to do this is 
>> to simply use the same property getter that is used when forwarding. This is 
>> the proposed solution.
>> 
>> Self return types
>> 
>> When a protocol member includes a Self return type forwarding 
>> implementations must return the forwarding type. However, the forwardee 
>> implmentation will return a value of the forwardee type. This result must be 
>> used to produce a value of the forwarding type in some way.
>> 
>> The solution in this proposal is based on an ad-hoc overloading convention. 
>> A protocol-based solution would probably be desirable if it were possible, 
>> however it is not. This proposal supports forwarding to more than one 
>> member, possibly with different types. A protocol-based solution would 
>> require the forwarding type to conform to the “Self return value conversion” 
>> protocol once for each forwardee type.
>> 
>> Static members
>> 
>> When a forwardee value is returned from a static member an initializer will 
>> be used to produce a final return value. The initializer must be visible at 
>> the source location of the forward declaration and must look like this:
>> 
>> struct Forwarder {
>>     let forwardee: Forwardee
>>     forward P to forwardee
>>     init(_ forwardeeReturnValue: Forwardee) { //... }
>> }
>> Instance members
>> 
>> When a forwardee value is returned from an instance member an instance 
>> method will be used to transform the return value into a value of the 
>> correct type. An instance method is necessary in order to allow the 
>> forwarding type to access the state of the instance upon which the method 
>> was called when performing the transformation.
>> 
>> If the instance method is not implemented the initializer used for static 
>> members will be used instead.
>> 
>> The transformation has the form:
>> 
>> struct Forwarder {
>>     let forwardee: Forwardee
>>     forward P to forwardee
>>     func transformedForwardingReturnValue(forwardeeReturnValue: Forwardee) 
>> -> Forwarder { //... }
>> }
>> NOTE: This method should have a better name. Suggestions are appreciated!
>> 
>> Examples
>> 
>> Basic example
>> 
>> NOTE: Forwardee does not actually conform to P itself. Conformance is not 
>> required to synthesize the forwarding member implementations. It is only 
>> required that members necessary for forwarding exist. This is particularly 
>> important to the second example.
>> 
>> public protocol P {
>>     typealias TA
>>     var i: Int
>>     func foo() -> Bool
>> }
>> 
>> private struct Forwardee {
>>     typealias TA = String
>>     var i: Int = 42
>>     func foo() -> Bool { return true }
>> }
>> 
>> public struct Forwarder {
>>     private let forwardee: Forwardee
>> }
>> 
>> extension Forwarder: P {     
>>     // user declares
>>     public forward P to forwardee
>>     
>>     // compiler synthesizes
>>     // TA must be synthesized as it cannot be inferred for this protocol
>>     public typealias TA = String 
>>     public var i: Int {
>>         get { return forwardee.i }
>>         set { forwardee.i = newValue }
>>     }
>>     public func foo() -> Bool { 
>>         return forwardee.foo() 
>>     }
>> }
>> Existential forwardee
>> 
>> NOTE: Existentials of type P do not actually conform to P itself. 
>> Conformance is not required to synthesize the forwarding member 
>> implementations. It is only required that members necessary for forwarding 
>> exist.
>> 
>> public protocol P {
>>     func foo() -> Bool
>> }
>> 
>> struct S: P {
>>     private let p: P
>>     
>>     // user declares:
>>     forward P to p
>>     
>>     // compiler synthesizes:
>>     func foo() -> Bool {
>>         return p.foo()
>>     }
>> }
>> Self parameters
>> 
>> public protocol P {
>>     func foo(value: Self) -> Bool
>> }
>> 
>> extension Int: P {
>>     func foo(value: Int) -> Bool {
>>         return value != self
>>     }
>> }
>> 
>> struct S: P {
>>     private let i: Int
>>     
>>     // user declares:
>>     forward P to i
>>     
>>     // compiler synthesizes:
>>     func foo(value: S) -> Bool {
>>         return i.foo(value.i)
>>     }
>> }
>> Self return types
>> 
>> Using the instance method:
>> 
>> public protocol P {
>>     func foo() -> Self
>> }
>> 
>> extension Int: P {
>>     func foo() -> Int {
>>         return self + 1
>>     }
>> }
>> 
>> struct S: P {
>>     private let i: Int
>>     func transformedForwardingReturnValue(forwardeeReturnValue: Int) -> S {
>>         return S(i: forwardeeReturnValue)
>>     }
>>     
>>     // user declares:
>>     forward P to i
>>     
>>     // compiler synthesizes:
>>     func foo() -> S {
>>         return self.transformedForwardingReturnValue(i.foo())
>>     }
>> }
>> Using the initializer:
>> 
>> public protocol P {
>>     func foo() -> Self
>> }
>> 
>> extension Int: P {
>>     func foo() -> Int {
>>         return self + 1
>>     }
>> }
>> 
>> struct S: P {
>>     private let i: Int
>>     init(_ value: Int) {
>>         i = value
>>     }
>>     
>>     // user declares:
>>     forward P to i
>>     
>>     // compiler synthesizes:
>>     func foo() -> S {
>>         return S(i.foo())
>>     }
>> }
>> Forwarding multiple protocols
>> 
>> public protocol P {
>>     func foo() -> Bool
>> }
>> public protocol Q {
>>     func bar() -> Bool
>> }
>> 
>> extension Int: P, Q {
>>     func foo() -> Bool {
>>         return true
>>     }
>>     func bar() -> Bool {
>>         return false
>>     }
>> }
>> 
>> struct S: P, Q {
>>     private let i: Int
>>     
>>     // user declares:
>>     forward P, Q to i
>>     
>>     // compiler synthesizes:
>>     func foo() -> Bool {
>>         return i.foo()
>>     }
>>     func bar() -> Bool {
>>         return i.bar()
>>     }
>> }
>> Forwarding to multiple members
>> 
>> public protocol P {
>>     func foo() -> Bool
>> }
>> public protocol Q {
>>     func bar() -> Bool
>> }
>> 
>> extension Int: P {
>>     func foo() -> Bool {
>>         return true
>>     }
>> }
>> extension Double: Q {
>>     func bar() -> Bool {
>>         return false
>>     }
>> }
>> 
>> struct S: P, Q {
>>     private let i: Int
>>     private let d: Double
>>     
>>     // user declares:
>>     forward P to i
>>     forward Q to d
>>     
>>     // compiler synthesizes:
>>     func foo() -> Bool {
>>         return i.foo()
>>     }
>>     func bar() -> Bool {
>>         return d.bar()
>>     }
>> }
>> Non-final class
>> 
>> NOTE: C cannot declare conformance to the protocol due to the Self return 
>> value requirement. However, the compiler still synthesizes the forwarding 
>> methods and allows them to be used directly by users of C.
>> 
>> public protocol P {
>>     func foo() -> Self
>> }
>> 
>> extension Int: P {
>>     func foo() -> Int {
>>         return self + 1
>>     }
>> }
>> 
>> // C does not and cannot declare conformance to P
>> class C {
>>     private let i: Int
>>     init(_ value: Int) {
>>         i = value
>>     }
>>     
>>     // user declares:
>>     forward P to i
>>     
>>     // compiler synthesizes:
>>     func foo() -> C {
>>         return C(i.foo())
>>     }
>> }
>> Detailed design
>> 
>> TODO: grammar modification to add the forward declaration
>> 
>> Automatic forwarding only synthesizes member implementations. It does not 
>> automatically conform the forwarding type to the protocol(s) that are 
>> forwarded. If actual conformance is desired (as it usually will be) it must 
>> be explicitly stated.
>> The forwardee type need not actually conform to the protocol forwarded to 
>> it. It only needs to implement the members the forwarder must access in the 
>> synthesized forwarding methods. This is particularly important as long as 
>> protocol existentials do not conform to the protocol itself.
>> While it will not be possible to conform non-final classes to protocols 
>> containing a Self return type forwarding should still be allowed. The 
>> synthesized methods will have a return type of the non-final class which in 
>> which the forwarding declaration occured. The synthesized methods may still 
>> be useful in cases where actual protocol conformance is not necessary.
>> All synthesized members recieve access control modifiers matching the access 
>> control modifier applied to the forward declaration.
>> TODO: How should other annotations on the forwardee implementations of 
>> forwarded members (such as @warn_unused_result) be handled? 
>> It is possible that the member implementations synthesized by forwarding 
>> will conflict with existing members or with each other (when forwarding more 
>> than one protocol). All such conflicts, with one exception, should produce a 
>> compiler error at the site of the forwarding declaration which resulted in 
>> conflicting members.
>> One specific case that should not be considered a conflict is when 
>> forwarding more than one protocol with identical member declarations to the 
>> same member of the forwarding type. In this case the synthesized 
>> implementation required to forward all of the protocols is identical. The 
>> compiler should not synthesize multiple copies of the implementation and 
>> then report a redeclaration error.
>> It is likely that any attempt to forward different protocols with Self 
>> return types to more than one member of the same type will result in 
>> sensible behavior. This should probable be a compiler error. For example:
>> protocol P {
>>     func foo() -> Self
>> }
>> protocol Q {
>>     func bar() -> Self
>> }
>> 
>> struct Forwarder: P, Q {
>>     let d1: Double
>>     let d2: Double
>>     
>>     forward P to d1
>>     forward Q to d2
>> 
>>     func transformedForwardingReturnValue(_ forwardeeReturnValue: Double) -> 
>> Forwarder { 
>>         // What do we do here?  
>>         // We don't know if the return value resulted from forwarding foo to 
>> d1 or bar to d2.
>>         // It is unlikely that the same behavior is correct in both cases.
>>     }
>> }
>> Impact on existing code
>> 
>> This is a strictly additive change. It has no impact on existing code.
>> 
>> Future enhancements
>> 
>> In the spirit of incremental change, this proposal focuses on core 
>> functionality. Several enhancements to the core functionality are possible 
>> and are likely to be explored in the future.
>> 
>> Partial forwarding synthesis
>> 
>> The current proposal makes automatic forwarding an “all or nothing” feature. 
>> In cases where you want to forward most of the implementation of a set of 
>> members but would need to “override” one or more specific members the 
>> current proposal will not help. You will still be required to forward the 
>> entire protocol manually. Attempting to implement some specific members 
>> manually will result in a redeclaration error.
>> 
>> This proposal does not allow partial forwarding synthesis in order to focus 
>> on the basic forwarding mechanism and allow us to gain some experience with 
>> that first, before considering the best way to make partial forwarding 
>> possible without introducing unintended potential for error. One example of 
>> a consideration that may apply is whether or not forwardee types should be 
>> able to mark members as “final for forwarding” in some way that prevents 
>> them from being “overriden” by a forwarder.
>> 
>> Newtype
>> 
>> While the current proposal provides the basic behavior desired for newtype, 
>> it is not as concise as it could be. Adding syntactic sugar to make this 
>> common case more concise would be straightforward:
>> 
>> // user declares
>> newtype Weight = Double forwarding P, Q
>> 
>> // compiler synthesizes
>> struct Weight: P, Q {
>>     var value: Double
>>     forward P, Q to value
>>     init(_ value: Double) { self.value = value }
>> }
>> However, there are additional nuances related to associated types 
>> <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151228/004735.html>
>>  that should be considered and addressed by a newtype proposal.
>> 
>> Forwarding declaration in protocol extensions
>> 
>> It may be possible to allow the forward declaration in protocol extensions 
>> by forwarding to a required property of the protocol. This may have 
>> implementation complexities and other implications which would hold back the 
>> current proposal if it required the forward declaration to be allowed in 
>> protocol extensions. 
>> 
>> Alternatives considered
>> 
>> Specify forwarding as part of the member declaration
>> 
>> I originally thought it would make the most sense to specify forwarding 
>> alongside the forwardee member declaration. This proposal does not do so for 
>> the following reasons:
>> 
>> We must be able to specify access control for the forwarded members that are 
>> synthesized. Introducing a forwarding declaration is the most clear way to 
>> allow this.
>> It will sometimes be necessary to forward different protocols to the same 
>> forwardee with different access control levels. It would be very clunky to 
>> do this as part of the member declaration.
>> It should be possible to synthesize forwarding retroactively as part of an 
>> extension. This would not be possible if forwarding had to be specified in 
>> the original member declaration.
>> Require the forwardee to conform to the protocol(s) forwarded to it
>> 
>> There is not a compelling reason to require this. It is not necessary to 
>> synthesize and compile the forwarding methods and it would prevent the use 
>> of protocol existentials as the forwardee.
>> 
>> Automatically conform the forwarding type to the forwarded protocol(s)
>> 
>> It may seem reasonable to automatically synthesize conformance to the 
>> protocol in addition to the member implementations. This proposal does not 
>> do so for the following reasons:
>> 
>> Forwarding is considered an implementation detail that is not necessarily 
>> visible in the public interface of the type. The forwardee may be a private 
>> member of the type.
>> Type authors may wish to control where the actual conformance is declared, 
>> especially if protocol conformances are allowed to have access control in 
>> the future.
>> There may be use cases where it is desirable to have the forwarded members 
>> synthesized without actually conforming to the protocol. This is somewhat 
>> speculative, but there is not a compelling reason to disallow it.
>> Allow forwarding of all protocols conformed to by the forwardee without 
>> explicitly listing them
>> 
>> It may seem reasonable to have a * placeholder which will forward all 
>> visible protocol conformances of the forwardee type. This proposal does not 
>> include such a placeholder for the following reasons:
>> 
>> A placeholder like this could lead to unintended operations being 
>> synthesized if additional conformances are declared in the future. The new 
>> conformances could even lead to conflicts during synthesis which cause the 
>> code to fail to compile. The potential for such breakage is not acceptable.
>> A placeholder like this would not necessarily cause all desired forwarding 
>> methods to be synthesized. This would be the case when the members necessary 
>> to conform exist but actual conformance does not exist. This would be the 
>> case when the forwardee type is an existential. This could lead to 
>> programmer confusion.
>> An explicit list of protocols to forward is not unduely burdensome. It is 
>> straightforward to declare a new protocol that inherits from a group of 
>> protocols which are commonly forwarded together and use the new protocol in 
>> the forwarding declaration.
>> This is easily added as a future enhancement to the current proposal if we 
>> later decide it is necessary.
>> Allow forwarding of the entire interface of the forwardee type, not just 
>> specific protocols
>> 
>> It is impossible to synthesize forwarding of methods which contain the 
>> forwardee type as a parameter or return type that are not declared as part 
>> of a protocol interface in a correct and safe manner. This is because it may 
>> or may not be correct to promote the forwardee type in the signature to the 
>> forwarder. 
>> 
>> As an example, consider the following extension to Double. Imagine trying to 
>> synthesize a forwarding method in a Pixel type that forwards to Double. 
>> Should the return type be Pixel or Double? It is impossible to tell for sure.
>> 
>> extension Double {
>>     func foo() -> Double {
>>         return self
>>     }
>> }
>> When the method is declared in a protocol it becomes obvious what the 
>> signature of the forwarding method must be. If the protocol declares the 
>> return type as Self, the forwarding method must have a return type of Pixel. 
>> If the protocol declares the return type as Double the forwarding method 
>> will continue to have a return type of Double.
>> 
>> Allow forwarding to Optional members
>> 
>> It may seem like a good idea to allow synthesized forwarding to Optional 
>> members where a no-op results when the Optional is nil. There is no way to 
>> make this work in general as it would be impossible to forward any member 
>> requiring a return value. If use cases for forwarding to Optionalmembers 
>> emerege that are restricted to protocols with no members requiring return 
>> values the automatic protocol forwarding feature could be enhanced in the 
>> future to support these use cases.
>> 
>> Allow forwarding the same protocol(s) to more than one member
>> 
>> As with forwarding to Optional members, forwarding the same protocol to more 
>> than one member is not possible in general. However it is possible in cases 
>> where no protocol members have a return value. If compelling use cases 
>> emerge to motivate automatic forwarding of such protocols to more than one 
>> member an enhancement could be proposed in the future.
>> 
>> Provide a mechanism for forwardees to know about the forwarder
>> 
>> Some types may be designed to be used as components that are always 
>> forwarded to by other types. Such types may wish to be able to communicate 
>> with the forwarding type in some way. This can be accomplished manually. 
>> 
>> If general patterns emerge in practice it may be possible to add support for 
>> them to the language. However, it would be preliminary to consider support 
>> for such a feature until we have significant experience with the basic 
>> forwarding mechanism itself.
>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to