[Proposal Draft] automatic protocol forwarding


(Matthew Johnson) #1

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.


Opaque result types
Newtype without automatic protocol forwarding
(Lily Ballard) #2

I briefly skimmed your proposal, so I apologize if you already addressed
this, but it occurs to me that we could support automatic protocol
forwarding today on a per-protocol basis simply by declaring a separate
protocol that provides default implementations doing the forwarding.
Handling of Self return types can then be done by adding a required
initializer (or just not implementing that method, so the concrete type
is forced to deal with it even though everything else is forwarded).

For example, if I want to automatically forward SequenceType to a
member, I can do something like

protocol SequenceTypeForwarder : SequenceType {

typealias ForwardedSequenceType : SequenceType

var forwardedSequence : ForwardedSequenceType { get }

}

extension SequenceTypeForwarder {

func generate() -> ForwardedSequenceType.Generator {

return forwardedSequence.generate()

}

func underestimateCount() -> Int {

return forwardedSequence.underestimateCount()

}

func map<T>(@noescape transform:
(ForwardedSequenceType.Generator.Element) throws -> T) rethrows -> [T] {

return try forwardedSequence.map(transform)

}

func filter(@noescape includeElement:
(ForwardedSequenceType.Generator.Element) throws -> Bool) rethrows ->
[ForwardedSequenceType.Generator.Element] {

return try forwardedSequence.filter(includeElement)

}

func forEach(@noescape body: (ForwardedSequenceType.Generator.Element)
throws -> Void) rethrows {

return try forwardedSequence.forEach(body)

}

func dropFirst(n: Int) -> ForwardedSequenceType.SubSequence {

return forwardedSequence.dropFirst(n)

}

func dropLast(n: Int) -> ForwardedSequenceType.SubSequence {

return forwardedSequence.dropLast(n)

}

func prefix(maxLength: Int) -> ForwardedSequenceType.SubSequence {

return forwardedSequence.prefix(maxLength)

}

func suffix(maxLength: Int) -> ForwardedSequenceType.SubSequence {

return forwardedSequence.suffix(maxLength)

}

func split(maxSplit: Int, allowEmptySlices: Bool, @noescape isSeparator:
(ForwardedSequenceType.Generator.Element) throws -> Bool) rethrows ->
[ForwardedSequenceType.SubSequence] {

return try forwardedSequence.split(maxSplit, allowEmptySlices:
allowEmptySlices, isSeparator: isSeparator)

}

}

With this protocol declared, I can then say something like

struct Foo {

var ary: [Int]

}

extension Foo : SequenceTypeForwarder {

var forwardedSequence: [Int] { return ary }

}

and my struct Foo now automatically implements SequenceType by
forwarding to its variable `ary`.

The downside to this is it needs to be manually declared for each
protocol. But I wager that most protocols actually aren't really
amenable to forwarding anyway.

-Kevin Ballard


(Brent Royal-Gordon) #3

I have completed a first draft of a proposal to introduce automatic protocol forwarding. I’m looking forward to feedback from everyone!

Some things I don't see discussed here:

* Does it have to be a protocol? Why not also allow the concrete type of the property you're forwarding to? Obviously you couldn't form a subtype relationship (unless you could...), but this might be useful to reduce boilerplate when you're proxying something.

* Why the method-based conversion syntax for return values, rather than something a little more like a property declaration?

  var number: Int
  forward IntegerType to number {
    static return(newValue: Int) {
      return NumberWrapper(newValue)
    }
    return(newValue: Int) {
      return NumberWrapper(newValue)
    }
  }

* If you want to keep the method-based syntax, why use the `init(_:)` initializer instead of one specifically for forwarding, like `init(forwardedReturnValue:)`?

* If you want to keep the method-based syntax, why do all forwards, even to different members, share the same transformation method? Wouldn't it be better to have, for instance, `init(returnedNumber:)` and `transformedReturnedNumber(_:)`, so that forwards to other properties could use different logic?

* If you want to keep the method-based syntax, would it make sense to instead have an initializer for instance initializers too, and just have it take a second parameter with the instance?

  init(forwardedReturnValue: Int) {...}
  init(forwardedReturnValue: Int, from: NumberWrapper) {...}

* Does this mean that a `public forward` declaration would forward `internal` members through synthesized `public` interfaces, if the forwarder and forwardee happened to be in the same module?

All synthesized members recieve access control modifiers matching the access control modifier applied to the forward declaration.

* You don't explicitly mention this, but I assume mutating methods work and mutate `self`?

···

--
Brent Royal-Gordon
Architechies


(Dave Abrahams) #4

Thanks for proposing this, Matthew!

The biggest thing missing here IMO (which has been missing from most of the other proposals I’ve seen) are examples of real-world problems solved by this proposal. I have no doubt some forwarding mechanism would be a huge benefit, but without clear examples, how do I know that this proposal actually addresses the need? One approach might be to show how the Lazy Collections subsystem in the standard library can be rewritten more simply using this mechanism.

-Dave

···

On Dec 29, 2015, at 8:37 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

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.


(Charles Srstka) #5

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.

Charles

···

On Dec 29, 2015, at 10:37 AM, Matthew Johnson via swift-evolution <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
https://lists.swift.org/mailman/listinfo/swift-evolution


(plx) #6

Thanks for writing this up.

Some quick points.

Firstly, I think it is best if the `init(_ forwardeeReturnValue: Forwardee)`-style initializer be replaced by something with a distinctly-named argument, e.g. `init(forwardeeReturnValue value: Forwardee)`.

For use with actual wrapper types the “init(_ wrappedValue: Wrapped)`-style init is too valuable to “claim” for this purpose (in particular b/c we may want to “adjust" the forwarded result); it’s IMHO better if we use a distinct `init` for the forwardee-return-value scenario so we know where the value is coming from.

Secondly, I would prefer partial-forwarding be given more consideration, b/c it seems somewhat common for me in practice at this time.

EG: I would do the following somewhat frequently:

struct FooIdentifier: Equatable, Comparable, Hashable

class Foo {
  let identifier: FooIdentifier
  let name: String

  forward Hashable to identifier
}

func ==(lhs: Foo, rhs: Foo) -> Bool {
  return lhs.identifier == rhs.identifier && lhs.name == rhs.name
}

…even though I agree that full-forwarding would the most-common scenario.

I have a few other similar cases involving non-standard-library types but they all fit the same basic pattern as above for `Hashable`.

Finally, I’m actually at a bit of a loss for too many uses for the generic forwarding mechanism; if it was added I’d use it to streamline some wrapper types, but beyond that I’m not seeing all that many places where I’d do much more than that with this feature if added.

Any chance at adding a few more-concrete motivating examples for the fancier cases?

···

On Dec 29, 2015, at 10:37 AM, Matthew Johnson via swift-evolution <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!


(Patrick Gili) #7

Just finished reading your write-up. It sounds similar to the Forwardable module supported by the Ruby standard library (http://ruby-doc.org/stdlib-2.0.0/libdoc/forwardable/rdoc/Forwardable.html). Is this the case?

Cheers,
-Patrick Gili


(Tino) #8

Have you looked at how this is handled in Kotlin?
Imho Jetbrains solution is quite elegant — especially as it also has a natural answer to another problem ("flexible memberwise initialization"; the whole init process is only a short chapter in the docs).
Of course, their approach is different, but I'd rather copy from the best than accept mediocrity because of the not invented here syndrome.

[short explanation: In Kotlin, we would have
class Forwarder(forwardee: Forwardee): P by forwardee {

}
forwardee would be available as a member, and because it is introduced before the protocol conformance, it can be used there without irritation.
Bonus: The designated initializer is defined at the same time.
]

As for the implementation:
Why not simply make this feature syntactic sugar and just auto-generate the forwarding methods?


(Joe Groff) #9

At least from a language-mechanics perspective, the only fundamental blocker I can think of that makes a protocol difficult to forward are nontrivial `Self` requirements, since you need a way to map from Foo<Forwarder> to Foo<Forwardee> anywhere the protocol requires Foo<Self>. There's quite a bit of that in the standard library thanks to the collection APIs, to be sure, but I wonder what the breakdown is in the wild for Self-inflicting protocols versus non outside of the stdlib.

I like the idea of using Forwarder protocols with default implementations, since it's amenable to macro generation and doesn't require new language features, but like Matt noted, it's easy to do the wrong thing from an API exposure standpoint by accidentally publishing your type's FooableForwarder conformance when you only wanted to promise that your type was Fooable.

-Joe

···

On Dec 29, 2015, at 12:06 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

The downside to this is it needs to be manually declared for each protocol. But I wager that most protocols actually aren't really amenable to forwarding anyway.


(Matthew Johnson) #10

Hi Kevin,

Thanks for taking time to look at the proposal.

The technique you show here is not bad, but it has several deficiencies IMO which are addressed by the solution in the proposal.

1. Forwarding should be an implementation detail, not exposed as it is with this method.
2. As such, the getter for the forwardee is visible. The forwardee is an implementation detail that should not be visible in most cases.
3. There is no way to specify access control for the synthesized methods and if there were it would be coupled to the access control of the conformance.
4. You can't forward to a type that doesn't conform to the protocol. This is particularly important for existential forwardees (at least until they conform to their protocol).
5. A type that can't conform to the protocol can't forward an implementation of the members of the protocol. Specifically, non-final classes cannot automatically forward a group of methods to an implementer.
6. This solution does not lay the foundation for a newtype feature. It would be possible to have a specialized newtype feature, but why not build it on top of a more general forwarding feature?

You may be right that many protocols are not amenable to forwarding. The motivation for this proposal is that it enables delegation-based designs to be implemented succinctly. In that use case the protocols will be designed alongside concrete implementations and types that forward to them. A secondary motivation for this proposal is to lay a foundation for a newtype feature. In that case the protocols to be forwarded would be specifically designed to represent the portion of the interface of the wrapped type which should be visible to users of the newtype.

I hope these points might be enough to convince you that it is worth a closer look.

Matthew

···

On Dec 29, 2015, at 2:06 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

I briefly skimmed your proposal, so I apologize if you already addressed this, but it occurs to me that we could support automatic protocol forwarding today on a per-protocol basis simply by declaring a separate protocol that provides default implementations doing the forwarding. Handling of Self return types can then be done by adding a required initializer (or just not implementing that method, so the concrete type is forced to deal with it even though everything else is forwarded).

For example, if I want to automatically forward SequenceType to a member, I can do something like

protocol SequenceTypeForwarder : SequenceType {
    typealias ForwardedSequenceType : SequenceType

    var forwardedSequence : ForwardedSequenceType { get }
}

extension SequenceTypeForwarder {
    func generate() -> ForwardedSequenceType.Generator {
        return forwardedSequence.generate()
    }

    func underestimateCount() -> Int {
        return forwardedSequence.underestimateCount()
    }

    func map<T>(@noescape transform: (ForwardedSequenceType.Generator.Element) throws -> T) rethrows -> [T] {
        return try forwardedSequence.map(transform)
    }

    func filter(@noescape includeElement: (ForwardedSequenceType.Generator.Element) throws -> Bool) rethrows -> [ForwardedSequenceType.Generator.Element] {
        return try forwardedSequence.filter(includeElement)
    }

    func forEach(@noescape body: (ForwardedSequenceType.Generator.Element) throws -> Void) rethrows {
        return try forwardedSequence.forEach(body)
    }

    func dropFirst(n: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.dropFirst(n)
    }

    func dropLast(n: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.dropLast(n)
    }

    func prefix(maxLength: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.prefix(maxLength)
    }

    func suffix(maxLength: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.suffix(maxLength)
    }

    func split(maxSplit: Int, allowEmptySlices: Bool, @noescape isSeparator: (ForwardedSequenceType.Generator.Element) throws -> Bool) rethrows -> [ForwardedSequenceType.SubSequence] {
        return try forwardedSequence.split(maxSplit, allowEmptySlices: allowEmptySlices, isSeparator: isSeparator)
    }
}

With this protocol declared, I can then say something like

struct Foo {
    var ary: [Int]
}

extension Foo : SequenceTypeForwarder {
    var forwardedSequence: [Int] { return ary }
}

and my struct Foo now automatically implements SequenceType by forwarding to its variable `ary`.

The downside to this is it needs to be manually declared for each protocol. But I wager that most protocols actually aren't really amenable to forwarding anyway.

-Kevin Ballard

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


(Félix Cloutier) #11

As the resident you-can-already-do-this grumpy guy, I tend to agree that most protocols aren't easily amenable to forwarding, and this solution works too.

In general, I think that Swift already doesn't impose too much boilerplate, and I think that most features to reduce boilerplate would be better served by having a sane macro system in Swift. (This is probably not in scope for Swift 3, though.)

Félix

···

Le 29 déc. 2015 à 15:06:48, Kevin Ballard via swift-evolution <swift-evolution@swift.org> a écrit :

I briefly skimmed your proposal, so I apologize if you already addressed this, but it occurs to me that we could support automatic protocol forwarding today on a per-protocol basis simply by declaring a separate protocol that provides default implementations doing the forwarding. Handling of Self return types can then be done by adding a required initializer (or just not implementing that method, so the concrete type is forced to deal with it even though everything else is forwarded).

For example, if I want to automatically forward SequenceType to a member, I can do something like

protocol SequenceTypeForwarder : SequenceType {
    typealias ForwardedSequenceType : SequenceType

    var forwardedSequence : ForwardedSequenceType { get }
}

extension SequenceTypeForwarder {
    func generate() -> ForwardedSequenceType.Generator {
        return forwardedSequence.generate()
    }

    func underestimateCount() -> Int {
        return forwardedSequence.underestimateCount()
    }

    func map<T>(@noescape transform: (ForwardedSequenceType.Generator.Element) throws -> T) rethrows -> [T] {
        return try forwardedSequence.map(transform)
    }

    func filter(@noescape includeElement: (ForwardedSequenceType.Generator.Element) throws -> Bool) rethrows -> [ForwardedSequenceType.Generator.Element] {
        return try forwardedSequence.filter(includeElement)
    }

    func forEach(@noescape body: (ForwardedSequenceType.Generator.Element) throws -> Void) rethrows {
        return try forwardedSequence.forEach(body)
    }

    func dropFirst(n: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.dropFirst(n)
    }

    func dropLast(n: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.dropLast(n)
    }

    func prefix(maxLength: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.prefix(maxLength)
    }

    func suffix(maxLength: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.suffix(maxLength)
    }

    func split(maxSplit: Int, allowEmptySlices: Bool, @noescape isSeparator: (ForwardedSequenceType.Generator.Element) throws -> Bool) rethrows -> [ForwardedSequenceType.SubSequence] {
        return try forwardedSequence.split(maxSplit, allowEmptySlices: allowEmptySlices, isSeparator: isSeparator)
    }
}

With this protocol declared, I can then say something like

struct Foo {
    var ary: [Int]
}

extension Foo : SequenceTypeForwarder {
    var forwardedSequence: [Int] { return ary }
}

and my struct Foo now automatically implements SequenceType by forwarding to its variable `ary`.

The downside to this is it needs to be manually declared for each protocol. But I wager that most protocols actually aren't really amenable to forwarding anyway.

-Kevin Ballard

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


(Dave Abrahams) #12

I briefly skimmed your proposal, so I apologize if you already addressed this, but it occurs to me that we could support automatic protocol forwarding today on a per-protocol basis simply by declaring a separate protocol that provides default implementations doing the forwarding. Handling of Self return types can then be done by adding a required initializer (or just not implementing that method, so the concrete type is forced to deal with it even though everything else is forwarded).

For example, if I want to automatically forward SequenceType to a member, I can do something like

protocol SequenceTypeForwarder : SequenceType {
    typealias ForwardedSequenceType : SequenceType

    var forwardedSequence : ForwardedSequenceType { get }
}

extension SequenceTypeForwarder {
    func generate() -> ForwardedSequenceType.Generator {
        return forwardedSequence.generate()
    }

    func underestimateCount() -> Int {
        return forwardedSequence.underestimateCount()
    }

    func map<T>(@noescape transform: (ForwardedSequenceType.Generator.Element) throws -> T) rethrows -> [T] {
        return try forwardedSequence.map(transform)
    }

    func filter(@noescape includeElement: (ForwardedSequenceType.Generator.Element) throws -> Bool) rethrows -> [ForwardedSequenceType.Generator.Element] {
        return try forwardedSequence.filter(includeElement)
    }

    func forEach(@noescape body: (ForwardedSequenceType.Generator.Element) throws -> Void) rethrows {
        return try forwardedSequence.forEach(body)
    }

    func dropFirst(n: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.dropFirst(n)
    }

    func dropLast(n: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.dropLast(n)
    }

    func prefix(maxLength: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.prefix(maxLength)
    }

    func suffix(maxLength: Int) -> ForwardedSequenceType.SubSequence {
        return forwardedSequence.suffix(maxLength)
    }

    func split(maxSplit: Int, allowEmptySlices: Bool, @noescape isSeparator: (ForwardedSequenceType.Generator.Element) throws -> Bool) rethrows -> [ForwardedSequenceType.SubSequence] {
        return try forwardedSequence.split(maxSplit, allowEmptySlices: allowEmptySlices, isSeparator: isSeparator)
    }
}

FWIW,

https://github.com/apple/swift/blob/master/stdlib/public/core/SequenceWrapper.swift

Though I don’t know why we still have this; it’s not used anywhere and should probably be removed. I think it was supposed to be part of the new lazy sequence/collection subsystem but it was never incorporated.

···

On Dec 29, 2015, at 12:06 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

With this protocol declared, I can then say something like

struct Foo {
    var ary: [Int]
}

extension Foo : SequenceTypeForwarder {
    var forwardedSequence: [Int] { return ary }
}

and my struct Foo now automatically implements SequenceType by forwarding to its variable `ary`.

The downside to this is it needs to be manually declared for each protocol. But I wager that most protocols actually aren't really amenable to forwarding anyway.

-Kevin Ballard

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


(Matthew Johnson) #13

I have completed a first draft of a proposal to introduce automatic protocol forwarding. I’m looking forward to feedback from everyone!

Some things I don't see discussed here:

* Does it have to be a protocol? Why not also allow the concrete type of the property you're forwarding to? Obviously you couldn't form a subtype relationship (unless you could...), but this might be useful to reduce boilerplate when you're proxying something.

This is addressed in the alternatives considered section. The short answer is that there the direct interface of the concrete type does not contain sufficient information about potential Self parameters to do this well. This information does exist in the protocol declarations. Allowing this information to be specified in concrete interfaces would add enough complexity to the language that I don’t think it is worthwhile. Joe Groff and I discussed this briefly yesterday: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151228/004660.html.

What you *can* do is declare a protocol containing the generalized signatures (substituting Self where appropriate) of all of the members of your concrete interface that you wish to be available to forward. That is a one-time protocol declaration that allows you to forward the concrete interface as many times as you wish. It seems like a reasonable tradeoff.

* Why the method-based conversion syntax for return values, rather than something a little more like a property declaration?

  var number: Int
  forward IntegerType to number {
    static return(newValue: Int) {
      return NumberWrapper(newValue)
    }
    return(newValue: Int) {
      return NumberWrapper(newValue)
    }
  }

This is actually a really good idea to consider! I didn’t consider something like this mostly because I didn’t think of it. I’m going to seriously consider adopting an approach along these lines.

One possible advantage of the approach I used is that the initializer may already exist for other reasons and you would not need to do any extra work.

A big advantage of this approach is that it would work even when you are forwarding different protocols to more than one member with the same type.

* If you want to keep the method-based syntax, why use the `init(_:)` initializer instead of one specifically for forwarding, like `init(forwardedReturnValue:)`?

That was a somewhat arbitrary decision I suppose. I also considered having the compiler look for any initializer accepting the correct type regardless of label.

* If you want to keep the method-based syntax, why do all forwards, even to different members, share the same transformation method? Wouldn't it be better to have, for instance, `init(returnedNumber:)` and `transformedReturnedNumber(_:)`, so that forwards to other properties could use different logic?

That is a limitation of the approach I used and the proposal disallows you to do such forwarding because of the ambiguity. I am glad you identified a solution to that!

* If you want to keep the method-based syntax, would it make sense to instead have an initializer for instance initializers too, and just have it take a second parameter with the instance?

  init(forwardedReturnValue: Int) {...}
  init(forwardedReturnValue: Int, from: NumberWrapper) {…}

Part of the reason the instance method was used is because sometimes the right thing to do might be to mutate and then return self. Using an instance method gives you the flexibility to do that if necessary.

* Does this mean that a `public forward` declaration would forward `internal` members through synthesized `public` interfaces, if the forwarder and forwardee happened to be in the same module?

All synthesized members recieve access control modifiers matching the access control modifier applied to the forward declaration.

Yes, if the forwardee had internal visibility and the forwarder was public the forwarder could publicly forward the interface. This is intentional behavior. The forwardee may well be an internal implementation detail while the methods of the protocol are part of the public interface of the forwarder. It is possible to write code that does this manually today.

* You don't explicitly mention this, but I assume mutating methods work and mutate `self`?

Mutating methods are something I didn’t think about carefully yet. Thanks for pointing that out! But generally, yes a forwarding implementation of a mutating method would need to mutate the forwardee which is part of self, thus mutating self.

···

On Dec 29, 2015, at 3:08 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

--
Brent Royal-Gordon
Architechies


(Matthew Johnson) #14

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)?

···

On Dec 29, 2015, at 5:25 PM, Charles Srstka <cocoadev@charlessoft.com> wrote:

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


(Matthew Johnson) #15

Thanks for the feedback Dave! I agree that the motivation section is a bit light and will work on filling it out with more detail, including specific examples of how it could be used to address specific problems.

···

On Dec 29, 2015, at 5:32 PM, Dave Abrahams <dabrahams@apple.com> wrote:

On Dec 29, 2015, at 8:37 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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.

Thanks for proposing this, Matthew!

The biggest thing missing here IMO (which has been missing from most of the other proposals I’ve seen) are examples of real-world problems solved by this proposal. I have no doubt some forwarding mechanism would be a huge benefit, but without clear examples, how do I know that this proposal actually addresses the need? One approach might be to show how the Lazy Collections subsystem in the standard library can be rewritten more simply using this mechanism.

-Dave


(Matthew Johnson) #16

Have you looked at how this is handled in Kotlin?

I hadn’t, but I did look at a paper on a forwarding preprocessor for Java that was called Jaime. The way forwarding is declared is very similar to Kotlin so it may have been a predecessor: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.39.3374&rep=rep1&type=pdf <http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.39.3374&rep=rep1&type=pdf>

The initial approach I considered was similar. I show an example following your Kotlin example down below.

Imho Jetbrains solution is quite elegant — especially as it also has a natural answer to another problem ("flexible memberwise initialization"; the whole init process is only a short chapter in the docs).

I don’t want this thread to get distracted with memberwise initialization so please move back to the appropriate thread if you want to discuss further. However, I will briefly respond here.

Here is Kotlin:
class Person constructor(firstName: String, lastName: String) {
}

Here is Swift under my proposal:
class Person {
    var firstName: String
    var lastName: String
    // compiler synthesizes memberwise init
}

However, my proposal gives you a lot of additional flexibility:

1. It interacts well with access control
2. Partial memberwise initialization is possible
3. It allows memberwise initializers to accept non-memberwise parameters to initialize private state
4. More than one memberwise initializer is possible
5. Memberwise initialization of properties with declaration modifiers, behaviors / delegates is possible

And probably more. My approach was to design a solution that fits into the current Swift language and is orthogonal to other language features as much as possible.

If you feel Kotlin’s approach is better please respond to the memberwise initialization thread with some examples written in both Kotlin and in Swift using the memberwise initialization feature I am proposing to demonstrate how and why you think it is better.

Of course, their approach is different, but I'd rather copy from the best than accept mediocrity because of the not invented here syndrome.

Best is a judgement that depends on the criteria you are using to perform the evaluation. If it really is better using criteria appropriate for evaluating new features for Swift then of course that would be the way to go. I would never advocate different for the sake of different or NIH.

[short explanation: In Kotlin, we would have
class Forwarder(forwardee: Forwardee): P by forwardee {

}
forwardee would be available as a member, and because it is introduced before the protocol conformance, it can be used there without irritation.
Bonus: The designated initializer is defined at the same time.
]

One approach I considered would look like this:

class Forwarder: P {
    // I didn’t like `implements` but didn’t have a better idea before I abandoned this approach
    var forwardee: Forwardee implements P
}

With the memberwise initialization proposal you also have the initializer synthesized automatically. The only thing it doesn’t do that your Kotlin example does is automatically declare conformance. This was an intentional design decision because it allows for additional expressivity. This is addressed in the alternatives considered section of the proposal.

Another difference that is maybe subtle but I think important is that with the approach I considered forwarding is declared in the body of a type or extension which emphasizes the fact that forwarding is an implementation detail, not something users of the type should be concerned with.

This approach was abandoned as it leads to problems in expressivity and clarity. Please see alternatives considered for an elaboration of that. This is especially true with the new approach to handling Self values that Brent suggested. That approach requires additional syntax around the forwarding declaration, but adds both clarity and expressiveness.

It appears to me that you value conciseness very highly. I do value conciseness but I also value safety, clarity, and expressiveness.

As for the implementation:
Why not simply make this feature syntactic sugar and just auto-generate the forwarding methods?

That is exactly what this proposal does. Why do you feel it is not doing that?

···

On Dec 30, 2015, at 5:47 AM, Tino Heth <2th@gmx.de> wrote:


(Matthew Johnson) #17

Thanks for writing this up.

Thanks for responding with comments.

Some quick points.

Firstly, I think it is best if the `init(_ forwardeeReturnValue: Forwardee)`-style initializer be replaced by something with a distinctly-named argument, e.g. `init(forwardeeReturnValue value: Forwardee)`.

For use with actual wrapper types the “init(_ wrappedValue: Wrapped)`-style init is too valuable to “claim” for this purpose (in particular b/c we may want to “adjust" the forwarded result); it’s IMHO better if we use a distinct `init` for the forwardee-return-value scenario so we know where the value is coming from.

Did you look at the approach Brent suggested yesterday? I’m going to update the proposal using something similar to that. We won’t be claiming any init at all. It will be slightly less concise in the common case but will add clarity, expressiveness, and resolve problems related to forwarding to more than one member of the same type.

Secondly, I would prefer partial-forwarding be given more consideration, b/c it seems somewhat common for me in practice at this time.

If somebody from the core team was enthusiastic about including partial forwarding in the initial proposal rather than as an enhancement that would certainly motivate me to reconsider.

As I stated earlier and in the proposal, one reason I left it out is that partial forwarding introduces similar concerns that arise with subclassing. Should a protocol or a type designed to be a forwardee be able to include annotations indicating that some members cannot be “overriden” by a forwarder? I haven’t had time to fully consider whether that is a good idea or not and if it is, what it would look like, how it should work, etc. I am reluctant to introduce partial forwarding without really thinking this through.

EG: I would do the following somewhat frequently:

struct FooIdentifier: Equatable, Comparable, Hashable

class Foo {
let identifier: FooIdentifier
let name: String

forward Hashable to identifier
}

func ==(lhs: Foo, rhs: Foo) -> Bool {
return lhs.identifier == rhs.identifier && lhs.name == rhs.name
}

Thanks for providing an example! It looks like you would have two different instances representing the same entity, possibly one serving as an edit buffer. Is that why they might have the same identifier but different names?

…even though I agree that full-forwarding would the most-common scenario.

I have a few other similar cases involving non-standard-library types but they all fit the same basic pattern as above for `Hashable`.

Finally, I’m actually at a bit of a loss for too many uses for the generic forwarding mechanism; if it was added I’d use it to streamline some wrapper types, but beyond that I’m not seeing all that many places where I’d do much more than that with this feature if added.

Any chance at adding a few more-concrete motivating examples for the fancier cases?

Yes, this is needed and I am working on it now.

···

On Dec 30, 2015, at 10:06 AM, plx via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 29, 2015, at 10:37 AM, Matthew Johnson via swift-evolution <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!

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


(Matthew Johnson) #18

Thanks for writing this up.

Some quick points.

Firstly, I think it is best if the `init(_ forwardeeReturnValue: Forwardee)`-style initializer be replaced by something with a distinctly-named argument, e.g. `init(forwardeeReturnValue value: Forwardee)`.

For use with actual wrapper types the “init(_ wrappedValue: Wrapped)`-style init is too valuable to “claim” for this purpose (in particular b/c we may want to “adjust" the forwarded result); it’s IMHO better if we use a distinct `init` for the forwardee-return-value scenario so we know where the value is coming from.

Secondly, I would prefer partial-forwarding be given more consideration, b/c it seems somewhat common for me in practice at this time.

EG: I would do the following somewhat frequently:

struct FooIdentifier: Equatable, Comparable, Hashable

class Foo {
let identifier: FooIdentifier
let name: String

forward Hashable to identifier
}

func ==(lhs: Foo, rhs: Foo) -> Bool {
return lhs.identifier == rhs.identifier && lhs.name == rhs.name
}

I should have also mentioned a couple of other things here. First, you’re not getting much because this only forwards the hashValue property. But you could argue it is more clear and it does prevent you from making a silly mistake even though the forwarding implementation is really simple.

Second, even without partial forwarding it is possible to do this under the current proposal. You just need to declare a new protocol that only contains the member you wish to forward:

protocol HashValuable {
    public var hashValue: Int { get }
}

class Foo: Hashable {
let identifier: FooIdentifier
let name: String

forward HashValuable to identifier
}

func ==(lhs: Foo, rhs: Foo) -> Bool {
return lhs.identifier == rhs.identifier && lhs.name == rhs.name
}

I

···

On Dec 30, 2015, at 10:06 AM, plx via swift-evolution <swift-evolution@swift.org> wrote:

…even though I agree that full-forwarding would the most-common scenario.

I have a few other similar cases involving non-standard-library types but they all fit the same basic pattern as above for `Hashable`.

Finally, I’m actually at a bit of a loss for too many uses for the generic forwarding mechanism; if it was added I’d use it to streamline some wrapper types, but beyond that I’m not seeing all that many places where I’d do much more than that with this feature if added.

Any chance at adding a few more-concrete motivating examples for the fancier cases?

On Dec 29, 2015, at 10:37 AM, Matthew Johnson via swift-evolution <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!

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


(Matthew Johnson) #19

Just finished reading your write-up. It sounds similar to the Forwardable module supported by the Ruby standard library (http://ruby-doc.org/stdlib-2.0.0/libdoc/forwardable/rdoc/Forwardable.html). Is this the case?

It works somewhat differently, but it provides similar functionality. As far as I can tell the only thing Forwardable does that this proposal does not do is support member renaming. That is something that might make an interesting future enhancement to this proposal.

···

On Dec 30, 2015, at 11:33 AM, Patrick Gili <gili.patrick.r@gili-labs.com> wrote:

Cheers,
-Patrick Gili


(Tino) #20

I don’t want this thread to get distracted with memberwise initialization

Makes sense in general, but Kotlin solves those problems as a whole, and the major benefit of their approach is that everything fits together really fine.
But I'll skip everything that is not related to forwarding.

One approach I considered would look like this:

class Forwarder: P {
    // I didn’t like `implements` but didn’t have a better idea before I abandoned this approach
    var forwardee: Forwardee implements P
}

Honestly, I'm can't see the value this discarded variant has for this discussion… you have to pit your proposal against Kotlin if you really want to convince anyone of its superiority.

With the memberwise initialization proposal you also have the initializer synthesized automatically. The only thing it doesn’t do that your Kotlin example does is automatically declare conformance. This was an intentional design decision because it allows for additional expressivity. This is addressed in the alternatives considered section of the proposal.

Can you be more precise? Kotlin clearly states what a class is doing in its first line, with all expressivity that is necessary by practical means.

Another difference that is maybe subtle but I think important is that with the approach I considered forwarding is declared in the body of a type or extension which emphasizes the fact that forwarding is an implementation detail, not something users of the type should be concerned with.

But what is the benefit of this emphasis? No solution requires to make the details visible in the public interface, and the ability to bury an important thing like protocol conformance somewhere in the class implementation is no advantage for me.

This approach was abandoned as it leads to problems in expressivity and clarity. Please see alternatives considered for an elaboration of that. This is especially true with the new approach to handling Self values that Brent suggested. That approach requires additional syntax around the forwarding declaration, but adds both clarity and expressiveness.

I think there is little need to worry about expressiveness for a feature that most potential users will probably never utilize in real code — and I don't think options like not conforming to a protocol that is forwarded is a big win here. It looks to me like you are optimizing for very uncommon cases, and sacrificing ease of use in the situations that are the most common by far.

It appears to me that you value conciseness very highly. I do value conciseness but I also value safety, clarity, and expressiveness.

No, I value elegance and simplicity — they often lead to clarity and safety.

Why not simply make this feature syntactic sugar and just auto-generate the forwarding methods?

That is exactly what this proposal does. Why do you feel it is not doing that?

you're right, guess I mixed up the proposal with something else; so at least we agree on how it should work :wink:

I'm not saying Swift has to copy another language, but I doubt that anyone who knows Kotlin would actually consider to drop their solution in favor of what is currently discussed...

Tino