[Proposal Draft] automatic protocol forwarding

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.

This could theoretically be managed by access control on protocol conformances:

public struct Foo: internal SequenceTypeForwarder, public SequenceType { ... }

though that's even more boilerplatey, and makes it easy to accidentally expose more API than you intended to.

-Joe

···

On Dec 29, 2015, at 1:24 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

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 <mailto: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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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

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.

I was tempted to call it `_forwardedSequence` instead, but I chose not
to do that because the _prefix convention right now means "stdlib
things that we don't want to expose but have to do so because of
implementation details".

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.

I'm not sure what you mean by this. Access control for these
methods should work identically to access control for the original
protocol. The forwarder protocol would simply be declared with the
same access control.

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

It seems weird to me that your forwarding proposal allows you to forward
to a member that doesn't conform to the protocol. I suppose it makes
some sense if you only forward some methods and implement the others
yourself, but I'm not convinced there's actually a good reason to
support this. What circumstances are you thinking of where you'd
actually find yourself forwarding to a member that doesn't conform to
the protocol (that isn't an existential)? The only case that really
comes to mind is when the member is of a generic type that can't conform
to the protocol (e.g. Array<Int> doesn't conform to Equatable), but the
solution there is to allow for conditional protocol conformance (which
we already know is something we want in the language).

Forwarding to existentials is a valid concern, but I'm not actually sure
why Swift doesn't make existential protocol values conform to the
protocol anyway. That limitation would make sense if you could create
existential protocol values from protocols with Self/associated types
where the existential simply omits the relevant members (because then it
obviously doesn't conform to the protocol), but Swift doesn't actually
let you create existentials in that situation anyway.

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.

It sounds like you're talking here about the ability to forward members
that aren't actually associated with a protocol, right? I don't see why
you can't just declare a protocol in that case to represent the members
that you want to be able to forward.

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?

Barring a detailed newtype proposal, it's hard to see how forwarding
would actually interact with it. Are you thinking that a newtype would
have some member that provides the underlying value (e.g. a `var
rawValue`)? Given such a member, a generalized forwarding mechanism
would then interact with it. But if e.g. newtype works by letting you
cast (with `as`) to the underlying type, then your generalized
forwarding mechanism can't actually work unless it has two different
modes (one of which uses a member and the other uses a cast), which
means you're specializing for newtype anyway.

Besides, when using a newtype mechanism like that, since the newtype has
the same in-memory representation as the original type, you don't
actually want to generate new methods at all for forwarding, instead you
just want to re-use the exact same methods as the original type
(otherwise you're just bloating your code with a bunch of stubs that do
nothing more than bitcast the value and call the original method).

-Kevin Ballard

···

On Tue, Dec 29, 2015, at 01:24 PM, Matthew Johnson wrote:

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

extensionSequenceTypeForwarder { 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] }

extensionFoo : 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

That's an interesting approach.

Another workaround for this today is to use an operator (similar to how
the stdlib uses ~> internally for a lot of stuff) coupled with a phantom
type. As an example (using ~> because why not):

// phantom type

struct _SequenceTypeForwarder {}

protocol SequenceTypeForwarder : SequenceType {

typealias ForwardedSequenceType : SequenceType

func ~>(this: Self, _: _SequenceTypeForwarder) -> ForwardedSequenceType

}

The need for the phantom type is a bit unfortunate (you can't just use
SequenceTypeForwarder.Protocol because it complains about the Self or
associated type requirements issue).

-Kevin Ballard

···

On Tue, Dec 29, 2015, at 02:11 PM, Joe Groff wrote:

On Dec 29, 2015, at 1:24 PM, Matthew Johnson via swift-evolution <swift- >> evolution@swift.org> wrote:

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.

This could theoretically be managed by access control on protocol
conformances:

public struct Foo: internal SequenceTypeForwarder, public
SequenceType { ... }

though that's even more boilerplatey, and makes it easy to
accidentally expose more API than you intended to.

* 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.

Sorry, I missed that, probably because the sample code in that section didn't show such a forwarding.

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.

That's a good point. You could perhaps add a way to tweak the forwarding of certain members, but that'd be a little tricky.

One of the things I'd like to see is the ability to proxy for an instance without the person writing the proxy knowing which instances it'll be used with. Think, for example, of the Cocoa animator proxy, or `NSUndoManager.prepareWithInvocationTarget(_:)`. It'd be nice if a Swift equivalent could return, say, `NSAnimatorProxy<View>` or `NSUndoManager.InvocationTarget<Target>`, which has all the methods of the generic type but records the calls for later use.

Of course, what you really want is for only a particular subset of the methods to be available on the proxy (animated methods on `NSAnimatorProxy`, Void methods on `NSUndoManager.InvocationTarget`), and of course in these cases you're not calling directly through to the underlying methods. So I might just be barking up the wrong tree here.

* 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.

Great.

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.

True. But it may also exist and *not* do what you want in the forwarding case. It's easier to explicitly use the right initializer than it is to work around the forwarding system implicitly using the wrong one.

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.

But again, if that's the wrong behavior, there's no good way to fix it.

* 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.

In practice, I'm not sure that's actually the case very often. How frequently will the internal type return a changed value, but your identically-named cover method ought to mutate the property? That completely changes the semantics of the underlying call.

I mean, what you're proposing would be something like this:

  class ListOfThings {
    private var actualList: [Thing]
    
    func filter(predicate: Thing -> Bool) -> ListOfThings {
      let returnValue = actualList.filter(predicate)
      actualList = returnValue
      return ListOfThings(returnValue)
    }
  }

Is that a thing you actually expect people to do?

* 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.

I suppose that, if it's always a protocol you're forwarding to, you can assume that none of the protocol methods are internal-only implementation details. But I have to admit that I'm still concerned about this; it just seems like a recipe for accidentally exposing things you meant to keep private.

* 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.

Well, as long as they're thought about at some point!

···

--
Brent Royal-Gordon
Architechies

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.

This could theoretically be managed by access control on protocol conformances:

public struct Foo: internal SequenceTypeForwarder, public SequenceType { ... }

though that's even more boilerplatey, and makes it easy to accidentally expose more API than you intended to.

Yep. I'd like to see access control on protocol conformances for other reasons. I've always figured that would be accomplished by allowing access control on extensions that declare protocol conformance rather than directly in the conformance itself. Both seem like reasonable approaches and aren't mutually exclusive.

I also brought up in point #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.

The coupling in this approach would still exist even with access control on conformances. The synthesized methods would always have the same visibility as the conformance, which may not always be desired.

We've talked in other threads about protocol forwarding a couple of times. Do you have any general reaction to the approach in this proposal?

···

Sent from my iPad

On Dec 29, 2015, at 4:11 PM, Joe Groff <jgroff@apple.com> wrote:

On Dec 29, 2015, at 1:24 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

-Joe

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

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

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.

I was tempted to call it `_forwardedSequence` instead, but I chose not to do that because the _prefix convention right now means "stdlib things that we don't want to expose but have to do so because of implementation details”.

Sure, but naming conventions are not access control.

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.

I'm not sure what you mean by this. Access control for these methods should work identically to access control for the original protocol. The forwarder protocol would simply be declared with the same access control.

There are 3 things at play here:

1. The protocol that is forwarded
2. The forwarding methods implementing the protocol requirements
3. Conformance to the protocol

When you write code manually it is possible to specify distinct access control for 1 and 2, and as Joe noted it would be nice to be able to specify access control for 3 as well. If this becomes possible all 3 could have distinct access levels.

For example, a public protocol, internal implementing methods, but private conformance. Or public implementing methods, an internal protocol, and private conformance. Or a public protocol, and internal implementing methods and conformance. Or public implementing methods, but an internal protocol and internal conformance.

Some of these combinations might be unusual, but there may be use cases for them. Ideally a forwarding mechanism would allow granular control, just as is possible with manual forwarding implementations.

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

It seems weird to me that your forwarding proposal allows you to forward to a member that doesn't conform to the protocol. I suppose it makes some sense if you only forward some methods and implement the others yourself, but I'm not convinced there's actually a good reason to support this. What circumstances are you thinking of where you'd actually find yourself forwarding to a member that doesn't conform to the protocol (that isn't an existential)? The only case that really comes to mind is when the member is of a generic type that can't conform to the protocol (e.g. Array<Int> doesn't conform to Equatable), but the solution there is to allow for conditional protocol conformance (which we already know is something we want in the language).

The proposal requires the forwardee to be *conformable* but not necessarily conforming. The difference is admittedly subtle, but there may be good reasons. It may be that it isn’t desriable for the forwardee to actually conform for one reason or another, maybe because you don’t want it to be possible to cast values of the forwardee type to the existential type of the protocol.

The reason the proposal doesn’t require actual conformance is simply because it isn’t necessary to synthesize the forwarding members and there are potential use cases that could take advantage of the additional flexibility. And again, because actual conformance would not be required to write the forwarding methods manually.

Protocols are an essential part of the forwarding mechanism because they are the only place in the language to differentiate between Self and a parameter or return value that should be always be of a specific type. However, actual conformance to the protocol by either the forwarder or the forwardee is not necessary to implement forwarding. Rather than arbitrarily require that it seems best to allow users control over whether, how, and where actual conformance is declared by both the forwarder and the forwardee.

The proposal specifically does not support “partial forwarding”. That was noted as a possible future enhancement. If such an enhancement were ever brought forward I would not support dropping the requirement for the forwardee to be “theoretically conformable” to the protocol. Partial forwarding would only exist to enable forwarders to “override” specific member implementations.

Forwarding to existentials is a valid concern, but I'm not actually sure why Swift doesn't make existential protocol values conform to the protocol anyway. That limitation would make sense if you could create existential protocol values from protocols with Self/associated types where the existential simply omits the relevant members (because then it obviously doesn't conform to the protocol), but Swift doesn't actually let you create existentials in that situation anyway.

Yes, I agree that existential should conform to the corresponding protocol and I hope it is a temporary limitation. It has been mentioned that there are some implementation complexities and possible performance issues to work through.

In any case, until this happens existential are a strong use case for taking advantage of forwarding to "theoretically conformable but not actually conforming” types. There is no reason not to support this use case and no reason not to enable any other use cases that might take advantage of that flexibility.

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.

It sounds like you're talking here about the ability to forward members that aren't actually associated with a protocol, right? I don't see why you can't just declare a protocol in that case to represent the members that you want to be able to forward.

No, I’m talking about the case where you might have a protocol with a Self return type. non-final classes cannot conform to such a protocol. But it is possible to synthesize forwarding methods corresponding to a protocol where the return type of those methods is Forwarder. You can’t declare conformance and don’t actually conform, but the forwarding methods could be synthesized without trouble and would work just fine in cases where a return type of Forwarder (rather than a covariant return type) is acceptable.

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?

Barring a detailed newtype proposal, it's hard to see how forwarding would actually interact with it. Are you thinking that a newtype would have some member that provides the underlying value (e.g. a `var rawValue`)? Given such a member, a generalized forwarding mechanism would then interact with it. But if e.g. newtype works by letting you cast (with `as`) to the underlying type, then your generalized forwarding mechanism can't actually work unless it has two different modes (one of which uses a member and the other uses a cast), which means you're specializing for newtype anyway.

Besides, when using a newtype mechanism like that, since the newtype has the same in-memory representation as the original type, you don't actually want to generate new methods at all for forwarding, instead you just want to re-use the exact same methods as the original type (otherwise you're just bloating your code with a bunch of stubs that do nothing more than bitcast the value and call the original method).

I roughly sketched what a newtype mechanism built on top of protocol forwarding might look like in a simple case in the future enhancements section of the proposal. This is not a detailed proposal by any means, but it does suggest a possible direction. The suggested direction is pretty similar to what Joe Groff mentioned in this post: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001137.html\.

···

On Dec 29, 2015, at 4:29 PM, Kevin Ballard <kevin@sb.org> wrote:
On Tue, Dec 29, 2015, at 01:24 PM, Matthew Johnson wrote:

-Kevin Ballard

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 <mailto: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 }
}

extensionSequenceTypeForwarder {
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]
}

extensionFoo : 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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

I see it in Future Enhancements, but not in Alternatives Considered (unless I’m just missing it). However, my thinking is that this isn’t really that hard to implement; just have the forwarding mechanism forward only methods that aren’t already implemented in the class. This would be similar to the way protocol extensions work, where if you implement the method you get that, but if you don’t implement it, you get the protocol extension’s implementation.

Charles

···

On Dec 29, 2015, at 5:38 PM, Matthew Johnson <matthew@anandabits.com> wrote:

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

Strong +1 on this proposal. I use Objective-C’s forwarding mechanisms quite often in my custom view code, in order to separate the code managing the outer view, the layout of subviews within the view, and business logic into separate classes, all while presenting a single, monolithic interface to the user. The loss of this ability without writing tons of boilerplate is one of the things about Swift that makes me sad.

The one thing I’d change is upgrading the partial forwarding synthesis to the original proposal, as that’s a rather important feature IMO.

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

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.

I did not but will make sure to read it.

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?

This is suffering from the “short examples will necessarily feel contrived, long examples are too long to be polite” problem. Sometimes it’s just from laziness — a good composite `hashValue` can be hard to write, so why not stick with a known-good `hashValue` implementation? — and often makes sense in context (if you know that same-ID, logically-different objects will be rare enough to have negligible overall impact).

I totally agree though I’m getting essentially nothing from forwarding here.

Similar scenarios can show up somewhat *artificially* in the context of generic code. Here’s a somewhat-contrived example:

Suppose you want to write a generic function that can look an “old” array and a “new” array and infer an “edit sequence” that can be applied to transform the “old” one into the “new” one, with an added complication: some values in the “new” one are *revisions* of values from the old one (and thus !=, but still “representing the same thing”).

This could be implemented a lot of different ways; one of them might look like this:

  protocol RecordIdentifierType : Equatable, Comparable, Hashable {} // etc
  protocol IdentifiableRecordType : Equatable, Hashable {
    typealias Identifier: RecordIdentifierType
    typealias Data: Equatable // weakest guarantee we need for what we plan to use this for
    // deliberately-clunky names to avoid accidental collisions:
    var recordIdentifier: Identifier { get }
    var recordData: Data { get }
  }

  func inferredEditOperations<R:IdentifiableRecordType>(
    forPreviousRecords previousRecords: [R],
    currentRecords: [R]) -> ArrayEditOperation {
    // exercise for reader
        }

…wherein 99% of the time a typical concrete conformance would just return `self` as `recordData` and some appropriate property as `recordIdentifier`, but it’d still be handy to have an “adaptor" type laying around like this:

  struct IdentifiableRecordAdaptor<I:RecordIdentifierType,D:Equatable> : IdentifiableRecordType {
    typealias Identifier = I
    typealias Data = D
    let recordIdentifier: I
    let recordData: D

    forwarding Hashable to RecordIdentifier
  }

…to support broader use of the generic algorithm in certain cases.

Note that this is still quite contrived — and it's still just `Equatable`/`Hashable` — but I think you can see how the scenario here could generalize in other contexts.

But at the same time, perhaps partial-forwarding is actually more of a niche use here than I thought?

···

On Dec 30, 2015, at 10:27 AM, Matthew Johnson <matthew@anandabits.com> wrote:

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?

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

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

+1

···

Sent from my iPad

On Dec 30, 2015, at 6:56 PM, Matthew Johnson <matthew@anandabits.com> wrote:

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

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.

Cheers,
-Patrick Gili

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.

Dave, thanks for pointing me to the Lazy Collections subsystem. It made for a really great case study!

A lot of what is happening in there is not directly forwarding related. But I do think the implementation of the parts that involve forwarding is improved by using the forwarding mechanism in this proposal. It is more clear and more robust than the current implementation.

As it turns out, _SequenceWrapperType and the extension to SequenceType in SequenceWrapper.swift actually are still in use. They contain an implementation of the forwarding mechanism Kevin Ballard suggested in this thread. _CollectionWrapperType and the extension to CollectionType are not in use. LazyCollection uses manual forwarding in the type itself which avoids some of the drawbacks of the protocol extension approach. Of course this begs the question of why two different mechanisms are in use and which is actually preferred.

I am working on a new draft of the proposal with a greatly expanded motivation section. I don’t have that completed yet, but I have completed a first pass of the section on the lazy collection subsystem. I am including the current draft here. I hope you find it interesting. I am interested in your thoughts on it.

Matthew
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. In the meantime, it facilitates similar functionality, although in a slightly more verbose manner.

Examples

Several examples follow.

The first two show how this proposal could improve how forwarding is implemented by the lazy collection subsystem of the standard library. This makes an interesting case study as each example employs a different forwarding mechanism.

LazySequence

The relevant portion of the current implementation of LazySequence looks like this (with comments removed and formatting tweaks):

// in SequenceWrapper.swift:

public protocol _SequenceWrapperType {
  typealias Base : SequenceType
  typealias Generator : GeneratorType = Base.Generator
  
  var _base: Base {get}
}

extension SequenceType
  where Self : _SequenceWrapperType, Self.Generator == Self.Base.Generator {

  public func generate() -> Base.Generator {
    return self._base.generate()
  }

  public func underestimateCount() -> Int {
    return _base.underestimateCount()
  }

  @warn_unused_result
  public func map<T>(
    @noescape transform: (Base.Generator.Element) throws -> T
  ) rethrows -> [T] {
    return try _base.map(transform)
  }

  @warn_unused_result
  public func filter(
    @noescape includeElement: (Base.Generator.Element) throws -> Bool
  ) rethrows -> [Base.Generator.Element] {
    return try _base.filter(includeElement)
  }
  
  public func _customContainsEquatableElement(
    element: Base.Generator.Element
  ) -> Bool? {
    return _base._customContainsEquatableElement(element)
  }
  
  public func _preprocessingPass<R>(@noescape preprocess: (Self) -> R) -> R? {
    return _base._preprocessingPass { _ in preprocess(self) }
  }

  public func _copyToNativeArrayBuffer()
    -> _ContiguousArrayBuffer<Base.Generator.Element> {
    return _base._copyToNativeArrayBuffer()
  }

  public func _initializeTo(ptr: UnsafeMutablePointer<Base.Generator.Element>)
    -> UnsafeMutablePointer<Base.Generator.Element> {
    return _base._initializeTo(ptr)
  }
}

// in LazySequence.swift:

public struct LazySequence<Base : SequenceType>
  : LazySequenceType, _SequenceWrapperType {

  public init(_ base: Base) {
    self._base = base
  }
  
  public var _base: Base
  public var elements: Base { return _base }
}
LazySequence is using the approach to forwarding mentioned by Kevin Ballard on the mailing list <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151228/004755.html&gt;in response to this proposal. This approach has several deficiencies that directly impact LazySequence:

LazySequence must publicly expose implementation details. Both its _base property as well as its conformance to _SequenceWrapperType.
The forwarding members must be manually implemented. They are trivial, but mistakes are still possible. In this case, @warn_unused_result is missing in some places where it should probably be specified (and would be synthesized using the approach in this proposal due to its presence in the protocol member declarations).
It is not immediately apparent that _SequenceWrapperType and the corresponding extension only provide forwarding members. Even if the name clearly indicates that it is possible that the code does something different. It is possible for somebody to come along after the initial implementation and add a new method that does something other than simple forwarding.
Because the forwarding is implemented via a protocol extension as default methods it can be overriden by an extension on LazySequence.
Here is an alternative implemented using the current proposal:

// _LazySequenceForwarding redeclares the subset of the members of SequenceType we wish to forward.
// The protocol is an implementation detail and is marked private.
private protocol _LazySequenceForwarding {

  typealias Generator : GeneratorType

  @warn_unused_result
  func generate() -> Generator

  @warn_unused_result
  func underestimateCount() -> Int

  @warn_unused_result
  func map<T>(
    @noescape transform: (Generator.Element) throws -> T
  ) rethrows -> [T]
  
  @warn_unused_result
  func filter(
    @noescape includeElement: (Generator.Element) throws -> Bool
  ) rethrows -> [Generator.Element]
  
  @warn_unused_result
  func _customContainsEquatableElement(
    element: Generator.Element
  ) -> Bool?

  func _copyToNativeArrayBuffer() -> _ContiguousArrayBuffer<Generator.Element>

  func _initializeTo(ptr: UnsafeMutablePointer<Generator.Element>)
    -> UnsafeMutablePointer<Generator.Element>
}

public struct LazySequence<Base : SequenceType> : LazySequenceType {

  public init(_ base: Base) {
    self._base = base
  }
  
  // NOTE: _base is now internal
  internal var _base: Base
  public var elements: Base { return _base }
  
  public forward _LazySequenceForwarding to _base
  
  // The current proposal does not currently support forwarding
  // of members with nontrivial Self requirements.
  // Because of this _preprocessingPass is forwarded manually.
  // A future enhancement may be able to support automatic
  // forwarding of protocols with some or all kinds of
  // nontrivial Self requirements.
  public func _preprocessingPass<R>(@noescape preprocess: (Self) -> R) -> R? {
    return _base._preprocessingPass { _ in preprocess(self) }
  }
}
This example takes advantage of a very important aspect of the design of this proposal. Neither Base nor LazySequence are required to conform to _LazySequenceForwarding. The only requirement is that Base contains the members specified in _LazySequenceForwarding as they will be used in the synthesized forwarding implementations.

The relaxed requirement is crucial to the application of the protocol forwarding feature in this implementation. We cannot conform Base to _LazySequenceForwarding. If it were possible to conform one protocol to another we could conform SequenceType to _LazySequenceForwarding, however it is doubtful that we would want that conformance. Despite this, it is clear to the compiler that Base does contain the necessary members for forwarding as it conforms to LazySequence which also declares all of the necessary members.

This implementation is more robust and more clear:

We no longer leak any implementation details.
There is no chance of making a mistake in the implementation of the forwarded members. It is possible that a mistake could be made in the member declarations in _LazySequenceForwarding. However, if a mistake is made there a compiler error will result.
The set of forwarded methods is immediately clear, with the exception of _preprocessingPass because of its nontrivial Self requirement. Removing the limitation on nontrivial Self requirements is a highly desired improvement to this proposal or future enhancement to this feature.
The forwarded members cannot be overriden in an extension on LazySequence. If somebody attempts to do so it will result in an ambiguous use error at call sites.
LazyCollection

The relevant portion of the current implementation of LazyCollection looks like this (with comments removed and formatting tweaks):

// in LazyCollection.swift:

public struct LazyCollection<Base : CollectionType>
  : LazyCollectionType {

  public typealias Elements = Base
  public var elements: Elements { return _base }

  public typealias Index = Base.Index

  public init(_ base: Base) {
    self._base = base
  }

  internal var _base: Base
}

extension LazyCollection : SequenceType {

  public func generate() -> Base.Generator { return _base.generate() }
  
  public func underestimateCount() -> Int { return _base.underestimateCount() }

  public func _copyToNativeArrayBuffer()
     -> _ContiguousArrayBuffer<Base.Generator.Element> {
    return _base._copyToNativeArrayBuffer()
  }
  
  public func _initializeTo(
    ptr: UnsafeMutablePointer<Base.Generator.Element>
  ) -> UnsafeMutablePointer<Base.Generator.Element> {
    return _base._initializeTo(ptr)
  }

  public func _customContainsEquatableElement(
    element: Base.Generator.Element
  ) -> Bool? {
    return _base._customContainsEquatableElement(element)
  }
}

extension LazyCollection : CollectionType {
    
  public var startIndex: Base.Index {
    return _base.startIndex
  }
  
  public var endIndex: Base.Index {
    return _base.endIndex
  }

  public subscript(position: Base.Index) -> Base.Generator.Element {
    return _base[position]
  }

  public subscript(bounds: Range<Index>) -> LazyCollection<Slice<Base>> {
    return Slice(base: _base, bounds: bounds).lazy
  }
  
  public var isEmpty: Bool {
    return _base.isEmpty
  }

  public var count: Index.Distance {
    return _base.count
  }

  public func _customIndexOfEquatableElement(
    element: Base.Generator.Element
  ) -> Index?? {
    return _base._customIndexOfEquatableElement(element)
  }

  public var first: Base.Generator.Element? {
    return _base.first
  }
}
LazyCollection is using direct manual implementations of forwarding methods. It corresponds exactly to implementations that would be synthesized by the compiler under this proposal. This approach avoids some of the problems with the first approach:

It does not leak implementation details. This is good!
The forwarded members cannot be overriden.
Unfortunately it still has some drawbacks:

It is still possible to make mistakes in the manual forwarding implementations.
The set of forwarded methods is even less clear than under the first approach as they are now potentially interspersed with custom, nontrivial member implementations, such as subscript(bounds: Range<Index>) -> LazyCollection<Slice<Base>> in this example.
This approach requires reimplementing the forwarded members in every type which forwards them and is therefore less scalable than the first approach and this proposal. This may not matter for LazyCollection but it may well matter in other cases.
One intersting difference to note between LazySequence and LazyCollection is that LazySequence forwards three members which LazyCollection does not: map, filter, and _preprocessingPass. It is unclear whether this difference is intentional or not.

This difference is particularly interesting in the case of _preprocessingPass. LazyCollectionappears to be using the default implementation for CollectionType in Collection.swift, which results in _base._preprocessingPass not getting called. It is not apparent why this behavior would be correct for LazyCollection and not for LazySequence.

I wonder if the difference in forwarded members is partly due to the fact that the set of forwarded members is not as clear as it could be.

Here is an alternate approach implemented using the current proposal. It assumes that the same SequenceType members that are forwarded by LazySequence should also be forwarded by LazyCollection, allowing us to reuse the _LazySequenceForwarding protocol declared in the first example.

// _LazyCollectionForwarding redeclares the subset of the members of Indexable and CollectionType we wish to forward.
// The protocol is an implementation detail and is marked private.
private protocol _LazyCollectionForwarding: _LazySequenceForwarding {
  typealias Index : ForwardIndexType
  var startIndex: Index {get}
  var endIndex: Index {get}

  typealias _Element
  subscript(position: Index) -> _Element {get}

  var isEmpty: Bool { get }
  var count: Index.Distance { get }
  var first: Generator.Element? { get }

  @warn_unused_result
  func _customIndexOfEquatableElement(element: Generator.Element) -> Index??
}

public struct LazyCollection<Base : CollectionType>
  : LazyCollectionType {

  public typealias Elements = Base
  public var elements: Elements { return _base }

  public init(_ base: Base) {
    self._base = base
  }

  internal var _base: Base
  
  public forward _LazyCollectionForwarding to _base

  // It may be the case that LazyCollection should forward _preprocessingPass
  // in the same fashion that LazySequence uses, which cannot yet be automated
  // under the current proposal.
}

extension LazyCollection : CollectionType {
  // This implementation is nontrivial and thus not forwarded
  public subscript(bounds: Range<Index>) -> LazyCollection<Slice<Base>> {
    return Slice(base: _base, bounds: bounds).lazy
  }
}
This approach to forwarding does not exhibit any of the issues with the manual approach and only takes about half as much code now that we are able to reuse the previous declaration of _LazySequenceForwarding.

NOTE: LazyMapCollection in Map.swift uses the same manual forwarding approach as LazyCollection to forward a handful of members and would therefore also be a candidate for adopting the new forwarding mechanism as well.

···

On Dec 29, 2015, at 5:44 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 29, 2015, at 12:06 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto: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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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

-Dave

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.

What I mean is this. In the example you gave and the syntax Kotlin uses:

class Forwarder(forwardee: Forwardee): P by forwardee {}

Forwarding is coupled to protocol conformance. This means I cannot use forwarding without conforming to the protocol that is forwarded.

Here is a quick example using the syntax of my proposal to demonstrate the difference:

class Forwarder {
  let forwardee: Forwardee
  forward P to forwardee
}

vs

class Forwarder: P {
  let forwardee: Forwardee
  forward P to forwardee
}

In the first example Forwarder does not conform to P. Forwarding is only used to synthesize the members of P. I am greatly expanding the motivation section of the proposal and will have examples showing where this is what you want. The lazy collections section I posted last night includes the first examples where this is the case.

In the second example here Forwarder does conform to P. The author of Forwarder has the flexibility to specify whether conformance is desired or not.

There are ways to handle that, including factoring the APIs of interest out of P and into a private protocol Q, then declaring the Forwardee’s conformance to Q. Now, there’s an expressivity problem with our current access control system that you can’t use an internal or private protocol to provide public API, but that should be fixed separately.

···

On Dec 31, 2015, at 7:33 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 31, 2015, at 5:04 AM, Tino Heth <2th@gmx.de <mailto:2th@gmx.de>> wrote:

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.

Protocol conformance is not buried in the implementation in my solution. I hope the previous example makes that clear. What is buried in the implementation is the forwarding declaration which causes the compiler to synthesize forwarding member implementations. This synthesis is an implementation detail and should not be visible outside the implementation.

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.

Like I stated, I am working on adding several examples of how this feature can be used in real code. Please have a look at the lazy collections example I shared last night. This example, as well as at least one other coming examples take advantage of the ability to use forwarding without requiring conformance.

As with the memberwise initialization proposal, the syntax you would like to see can easily be added as syntactic sugar on top of the current proposal. I would not support that as I do not like the syntax Kotlin uses for reasons already stated, but that shouldn’t stop you from pursuing a proposal for it. Maybe a lot of people would agree with you and it would be accepted.

Matthew

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

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

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

Sent from my iPad

-Dave

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.

What I mean is this. In the example you gave and the syntax Kotlin uses:

classs Forwarder(forwardee: Forwardee): P by forwardee {}

Forwarding is coupled to protocol conformance. This means I cannot use forwarding without conforming to the protocol that is forwarded.

Here is a quick example using the syntax of my proposal to demonstrate the difference:

class Forwarder {
  let forwardee: Forwardee
  forward P to forwardee
}

vs

class Forwarder: P {
  let forwardee: Forwardee
  forward P to forwardee
}

In the first example Forwarder does not conform to P. Forwarding is only used to synthesize the members of P. I am greatly expanding the motivation section of the proposal and will have examples showing where this is what you want. The lazy collections section I posted last night includes the first examples where this is the case.

In the second example here Forwarder does conform to P. The author of Forwarder has the flexibility to specify whether conformance is desired or not.

There are ways to handle that, including factoring the APIs of interest out of P and into a private protocol Q, then declaring the Forwardee’s conformance to Q. Now, there’s an expressivity problem with our current access control system that you can’t use an internal or private protocol to provide public API, but that should be fixed separately.

I'm not sure where Q comes into play in this specific example. The idea here is that forwarding implementations of all members of P are synthesized by the forward declaration. It is left up to Forwarder to decide whether or not to declare actual conformance to P. I am also confused by "then declaring the Forwardee’s conformance to Q" because we are discussing Forwarder's conformance here, not Forwardee’s.

Presumably both ends of the forwarding arrangement would have to conform to the same protocol, no?

What do you have in mind when you mention using a private or internal protocol to provide public API? It sounds like that might be interesting but I'm having trouble imagining what the syntax would look like and exactly how it would work. Is this something that is planned?

Not planned, but desired.

What might it look like?

Details need to be worked out. One thing we were doing for a while in the stdlib, before the rules got tightened and made it impossible, was

struct X : PublicProtocol, PrivateProtocol {
   ...
}

extension PublicProtocol where Self : PrivateProtocol {
   // API that uses only PublicProtocol in its implementation here
}

In any case, I don't see why that is related to requiring a Forwarder to conform to the forwarded protocol. There doesn't appear to me to be a good reason to require that and there are reasons not to require it. Protocols enable and drive the forwarding member synthesis mechanism but that mechanism doesn't need to require or provide conformance. It is a third major way to use protocols in addition to generic constraints and existential types.

Of course I could be wrong, but my instincts tell me that is an unneeded dimension of complexity, which is why I am resisting it. The generic/existential duality is already problematic in some ways, IMO.

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.

Protocol conformance is not buried in the implementation in my solution. I hope the previous example makes that clear. What is buried in the implementation is the forwarding declaration which causes the compiler to synthesize forwarding member implementations. This synthesis is an implementation detail and should not be visible outside the implementation.

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.

Like I stated, I am working on adding several examples of how this feature can be used in real code. Please have a look at the lazy collections example I shared last night. This example, as well as at least one other coming examples take advantage of the ability to use forwarding without requiring conformance.

As with the memberwise initialization proposal, the syntax you would like to see can easily be added as syntactic sugar on top of the current proposal. I would not support that as I do not like the syntax Kotlin uses for reasons already stated, but that shouldn’t stop you from pursuing a proposal for it. Maybe a lot of people would agree with you and it would be accepted.

Matthew

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

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

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

-Dave

···

On Dec 31, 2015, at 9:01 AM, Matthew Johnson <matthew@anandabits.com> wrote:
On Dec 31, 2015, at 10:09 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

On Dec 31, 2015, at 7:33 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Dec 31, 2015, at 5:04 AM, Tino Heth <2th@gmx.de <mailto:2th@gmx.de>> wrote:

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.

What I mean is this. In the example you gave and the syntax Kotlin uses:

class Forwarder(forwardee: Forwardee): P by forwardee {}

Forwarding is coupled to protocol conformance. This means I cannot use forwarding without conforming to the protocol that is forwarded.

Here is a quick example using the syntax of my proposal to demonstrate the difference:

class Forwarder {
  let forwardee: Forwardee
  forward P to forwardee
}

vs

class Forwarder: P {
  let forwardee: Forwardee
  forward P to forwardee
}

In the first example Forwarder does not conform to P. Forwarding is only used to synthesize the members of P. I am greatly expanding the motivation section of the proposal and will have examples showing where this is what you want. The lazy collections section I posted last night includes the first examples where this is the case.

In the second example here Forwarder does conform to P. The author of Forwarder has the flexibility to specify whether conformance is desired or not.

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.

Protocol conformance is not buried in the implementation in my solution. I hope the previous example makes that clear. What is buried in the implementation is the forwarding declaration which causes the compiler to synthesize forwarding member implementations. This synthesis is an implementation detail and should not be visible outside the implementation.

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.

Like I stated, I am working on adding several examples of how this feature can be used in real code. Please have a look at the lazy collections example I shared last night. This example, as well as at least one other coming examples take advantage of the ability to use forwarding without requiring conformance.

As with the memberwise initialization proposal, the syntax you would like to see can easily be added as syntactic sugar on top of the current proposal. I would not support that as I do not like the syntax Kotlin uses for reasons already stated, but that shouldn’t stop you from pursuing a proposal for it. Maybe a lot of people would agree with you and it would be accepted.

Matthew

···

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

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

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

Sent from my iPad

-Dave

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.

What I mean is this. In the example you gave and the syntax Kotlin uses:

classs Forwarder(forwardee: Forwardee): P by forwardee {}

Forwarding is coupled to protocol conformance. This means I cannot use forwarding without conforming to the protocol that is forwarded.

Here is a quick example using the syntax of my proposal to demonstrate the difference:

class Forwarder {
  let forwardee: Forwardee
  forward P to forwardee
}

vs

class Forwarder: P {
  let forwardee: Forwardee
  forward P to forwardee
}

In the first example Forwarder does not conform to P. Forwarding is only used to synthesize the members of P. I am greatly expanding the motivation section of the proposal and will have examples showing where this is what you want. The lazy collections section I posted last night includes the first examples where this is the case.

In the second example here Forwarder does conform to P. The author of Forwarder has the flexibility to specify whether conformance is desired or not.

There are ways to handle that, including factoring the APIs of interest out of P and into a private protocol Q, then declaring the Forwardee’s conformance to Q. Now, there’s an expressivity problem with our current access control system that you can’t use an internal or private protocol to provide public API, but that should be fixed separately.

I'm not sure where Q comes into play in this specific example. The idea here is that forwarding implementations of all members of P are synthesized by the forward declaration. It is left up to Forwarder to decide whether or not to declare actual conformance to P. I am also confused by "then declaring the Forwardee’s conformance to Q" because we are discussing Forwarder's conformance here, not Forwardee’s.

Presumably both ends of the forwarding arrangement would have to conform to the same protocol, no?

No. This is addressed in the proposal and the lazy collections motivating example I replied with last night. I don’t think it’s a good idea to require this.

The forwardee needs to implement the members of the protocol but does not need to conform. The forwarder will receive forwarding implementations of the members, but again does not need to declare conformance. Forwarding is orthogonal to conformance, just as it is today when you manually write forwarding members today.

What do you have in mind when you mention using a private or internal protocol to provide public API? It sounds like that might be interesting but I'm having trouble imagining what the syntax would look like and exactly how it would work. Is this something that is planned?

Not planned, but desired.

What might it look like?

Details need to be worked out. One thing we were doing for a while in the stdlib, before the rules got tightened and made it impossible, was

struct X : PublicProtocol, PrivateProtocol {
   ...
}

extension PublicProtocol where Self : PrivateProtocol {
   // API that uses only PublicProtocol in its implementation here
}

Presumably you implement public API here where members of PrivateProtocol can be accessed? This would address the problem of leaking implementation details when adding default forwarding implementations so it is definitely better than current state.

In any case, I don't see why that is related to requiring a Forwarder to conform to the forwarded protocol. There doesn't appear to me to be a good reason to require that and there are reasons not to require it. Protocols enable and drive the forwarding member synthesis mechanism but that mechanism doesn't need to require or provide conformance. It is a third major way to use protocols in addition to generic constraints and existential types.

Of course I could be wrong, but my instincts tell me that is an unneeded dimension of complexity, which is why I am resisting it. The generic/existential duality is already problematic in some ways, IMO.

I don’t really understand what is complex about it. Can you elaborate on why you think it introduces complexity? The idea seems pretty simple to me - if you can write a forwarding implementation that compiles and works the compiler should be able to synthesize it for you. I don’t see any reason to disallow that.

Is your concern that we may not be able to support forwarding of every protocol someone might come up with and there would be confusion around what protocols are eligible for forwarding and which aren’t (similar to the current confusion around which protocols can be used as existentials and which can’t)?

No, it just feels like an ad-hoc use of protocols to use their APIs as constraints without creating a conformance.

Isn’t the primary problem with generic / existential the fact that existentials are pretty limited currently? This is an unfortunate limitation and my understanding is that there is a desire to lift at least some parts of this limitation eventually, whether that starts to happen in Swift 3 or is a feature that comes later. [swift-evolution] Swift 3 Generics

Yes, there are plans to address this (and I’m looking forward to that!), but I think the difference is still going to be there and when to use one or the other is still going to remain a point of confusion.

One specific problem with requiring conformance is that forwarding to existentials would not be straightforward because they do not conform to their protocol. Obviously this is a limitation that should be lifted eventually but it isn’t clear when that might happen.

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.

Protocol conformance is not buried in the implementation in my solution. I hope the previous example makes that clear. What is buried in the implementation is the forwarding declaration which causes the compiler to synthesize forwarding member implementations. This synthesis is an implementation detail and should not be visible outside the implementation.

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.

Like I stated, I am working on adding several examples of how this feature can be used in real code. Please have a look at the lazy collections example I shared last night. This example, as well as at least one other coming examples take advantage of the ability to use forwarding without requiring conformance.

As with the memberwise initialization proposal, the syntax you would like to see can easily be added as syntactic sugar on top of the current proposal. I would not support that as I do not like the syntax Kotlin uses for reasons already stated, but that shouldn’t stop you from pursuing a proposal for it. Maybe a lot of people would agree with you and it would be accepted.

Matthew

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

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

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

-Dave

-Dave

···

On Dec 31, 2015, at 9:47 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Dec 31, 2015, at 11:18 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

On Dec 31, 2015, at 9:01 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:
On Dec 31, 2015, at 10:09 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

On Dec 31, 2015, at 7:33 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Dec 31, 2015, at 5:04 AM, Tino Heth <2th@gmx.de <mailto:2th@gmx.de>> wrote:

My only point was that if we’re going to require the forwarder to conform, we may as well require it of the forwardee. I realize that you don’t want to require either conformance.

-Dave

···

On Dec 31, 2015, at 9:47 AM, Matthew Johnson <matthew@anandabits.com> wrote:

In the second example here Forwarder does conform to P. The author of Forwarder has the flexibility to specify whether conformance is desired or not.

There are ways to handle that, including factoring the APIs of interest out of P and into a private protocol Q, then declaring the Forwardee’s conformance to Q. Now, there’s an expressivity problem with our current access control system that you can’t use an internal or private protocol to provide public API, but that should be fixed separately.

I'm not sure where Q comes into play in this specific example. The idea here is that forwarding implementations of all members of P are synthesized by the forward declaration. It is left up to Forwarder to decide whether or not to declare actual conformance to P. I am also confused by "then declaring the Forwardee’s conformance to Q" because we are discussing Forwarder's conformance here, not Forwardee’s.

Presumably both ends of the forwarding arrangement would have to conform to the same protocol, no?

No. This is addressed in the proposal and the lazy collections motivating example I replied with last night. I don’t think it’s a good idea to require this.

No. This is addressed in the proposal and the lazy collections motivating example I replied with last night. I don’t think it’s a good idea to require this.

Can you put your proposal on GitHub under your swift-evolution fork? It’s really hard to follow the current state of the proposal and the changes over time.

The forwardee needs to implement the members of the protocol but does not need to conform. The forwarder will receive forwarding implementations of the members, but again does not need to declare conformance. Forwarding is orthogonal to conformance, just as it is today when you manually write forwarding members today.

I cannot think of a time where I wanted this pseudo-conformance. The entire purpose of forwarding, that I’ve ever wanted or seen at least, is to act as a pass-through from the outer type’s conformance to a protocol to an internal specific implementor of that protocol without having to write all of the boiler-plate code to make that happen.

So I find it confusing that you say they are orthogonal.

-David

···

On Dec 31, 2015, at 9:47 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

-Dave

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.

What I mean is this. In the example you gave and the syntax Kotlin uses:

classs Forwarder(forwardee: Forwardee): P by forwardee {}

Forwarding is coupled to protocol conformance. This means I cannot use forwarding without conforming to the protocol that is forwarded.

Here is a quick example using the syntax of my proposal to demonstrate the difference:

class Forwarder {
  let forwardee: Forwardee
  forward P to forwardee
}

vs

class Forwarder: P {
  let forwardee: Forwardee
  forward P to forwardee
}

In the first example Forwarder does not conform to P. Forwarding is only used to synthesize the members of P. I am greatly expanding the motivation section of the proposal and will have examples showing where this is what you want. The lazy collections section I posted last night includes the first examples where this is the case.

In the second example here Forwarder does conform to P. The author of Forwarder has the flexibility to specify whether conformance is desired or not.

There are ways to handle that, including factoring the APIs of interest out of P and into a private protocol Q, then declaring the Forwardee’s conformance to Q. Now, there’s an expressivity problem with our current access control system that you can’t use an internal or private protocol to provide public API, but that should be fixed separately.

I'm not sure where Q comes into play in this specific example. The idea here is that forwarding implementations of all members of P are synthesized by the forward declaration. It is left up to Forwarder to decide whether or not to declare actual conformance to P. I am also confused by "then declaring the Forwardee’s conformance to Q" because we are discussing Forwarder's conformance here, not Forwardee’s.

What do you have in mind when you mention using a private or internal protocol to provide public API? It sounds like that might be interesting but I'm having trouble imagining what the syntax would look like and exactly how it would work. Is this something that is planned? What might it look like?

In any case, I don't see why that is related to requiring a Forwarder to conform to the forwarded protocol. There doesn't appear to me to be a good reason to require that and there are reasons not to require it. Protocols enable and drive the forwarding member synthesis mechanism but that mechanism doesn't need to require or provide conformance. It is a third major way to use protocols in addition to generic constraints and existential types.

···

Sent from my iPad

On Dec 31, 2015, at 10:09 AM, Dave Abrahams <dabrahams@apple.com> wrote:

On Dec 31, 2015, at 7:33 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

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

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.

Protocol conformance is not buried in the implementation in my solution. I hope the previous example makes that clear. What is buried in the implementation is the forwarding declaration which causes the compiler to synthesize forwarding member implementations. This synthesis is an implementation detail and should not be visible outside the implementation.

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.

Like I stated, I am working on adding several examples of how this feature can be used in real code. Please have a look at the lazy collections example I shared last night. This example, as well as at least one other coming examples take advantage of the ability to use forwarding without requiring conformance.

As with the memberwise initialization proposal, the syntax you would like to see can easily be added as syntactic sugar on top of the current proposal. I would not support that as I do not like the syntax Kotlin uses for reasons already stated, but that shouldn’t stop you from pursuing a proposal for it. Maybe a lot of people would agree with you and it would be accepted.

Matthew

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

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

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

Sent from my iPad

-Dave

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.

What I mean is this. In the example you gave and the syntax Kotlin uses:

classs Forwarder(forwardee: Forwardee): P by forwardee {}

Forwarding is coupled to protocol conformance. This means I cannot use forwarding without conforming to the protocol that is forwarded.

Here is a quick example using the syntax of my proposal to demonstrate the difference:

class Forwarder {
  let forwardee: Forwardee
  forward P to forwardee
}

vs

class Forwarder: P {
  let forwardee: Forwardee
  forward P to forwardee
}

In the first example Forwarder does not conform to P. Forwarding is only used to synthesize the members of P. I am greatly expanding the motivation section of the proposal and will have examples showing where this is what you want. The lazy collections section I posted last night includes the first examples where this is the case.

In the second example here Forwarder does conform to P. The author of Forwarder has the flexibility to specify whether conformance is desired or not.

There are ways to handle that, including factoring the APIs of interest out of P and into a private protocol Q, then declaring the Forwardee’s conformance to Q. Now, there’s an expressivity problem with our current access control system that you can’t use an internal or private protocol to provide public API, but that should be fixed separately.

I'm not sure where Q comes into play in this specific example. The idea here is that forwarding implementations of all members of P are synthesized by the forward declaration. It is left up to Forwarder to decide whether or not to declare actual conformance to P. I am also confused by "then declaring the Forwardee’s conformance to Q" because we are discussing Forwarder's conformance here, not Forwardee’s.

Presumably both ends of the forwarding arrangement would have to conform to the same protocol, no?

No. This is addressed in the proposal and the lazy collections motivating example I replied with last night. I don’t think it’s a good idea to require this.

The forwardee needs to implement the members of the protocol but does not need to conform. The forwarder will receive forwarding implementations of the members, but again does not need to declare conformance. Forwarding is orthogonal to conformance, just as it is today when you manually write forwarding members today.

What do you have in mind when you mention using a private or internal protocol to provide public API? It sounds like that might be interesting but I'm having trouble imagining what the syntax would look like and exactly how it would work. Is this something that is planned?

Not planned, but desired.

What might it look like?

Details need to be worked out. One thing we were doing for a while in the stdlib, before the rules got tightened and made it impossible, was

struct X : PublicProtocol, PrivateProtocol {
   ...
}

extension PublicProtocol where Self : PrivateProtocol {
   // API that uses only PublicProtocol in its implementation here
}

Presumably you implement public API here where members of PrivateProtocol can be accessed? This would address the problem of leaking implementation details when adding default forwarding implementations so it is definitely better than current state.

In any case, I don't see why that is related to requiring a Forwarder to conform to the forwarded protocol. There doesn't appear to me to be a good reason to require that and there are reasons not to require it. Protocols enable and drive the forwarding member synthesis mechanism but that mechanism doesn't need to require or provide conformance. It is a third major way to use protocols in addition to generic constraints and existential types.

Of course I could be wrong, but my instincts tell me that is an unneeded dimension of complexity, which is why I am resisting it. The generic/existential duality is already problematic in some ways, IMO.

I don’t really understand what is complex about it. Can you elaborate on why you think it introduces complexity? The idea seems pretty simple to me - if you can write a forwarding implementation that compiles and works the compiler should be able to synthesize it for you. I don’t see any reason to disallow that.

Is your concern that we may not be able to support forwarding of every protocol someone might come up with and there would be confusion around what protocols are eligible for forwarding and which aren’t (similar to the current confusion around which protocols can be used as existentials and which can’t)?

Isn’t the primary problem with generic / existential the fact that existentials are pretty limited currently? This is an unfortunate limitation and my understanding is that there is a desire to lift at least some parts of this limitation eventually, whether that starts to happen in Swift 3 or is a feature that comes later. [swift-evolution] Swift 3 Generics

One specific problem with requiring conformance is that forwarding to existentials would not be straightforward because they do not conform to their protocol. Obviously this is a limitation that should be lifted eventually but it isn’t clear when that might happen.

···

On Dec 31, 2015, at 11:18 AM, Dave Abrahams <dabrahams@apple.com> wrote:

On Dec 31, 2015, at 9:01 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:
On Dec 31, 2015, at 10:09 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

On Dec 31, 2015, at 7:33 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Dec 31, 2015, at 5:04 AM, Tino Heth <2th@gmx.de <mailto:2th@gmx.de>> wrote:

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.

Protocol conformance is not buried in the implementation in my solution. I hope the previous example makes that clear. What is buried in the implementation is the forwarding declaration which causes the compiler to synthesize forwarding member implementations. This synthesis is an implementation detail and should not be visible outside the implementation.

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.

Like I stated, I am working on adding several examples of how this feature can be used in real code. Please have a look at the lazy collections example I shared last night. This example, as well as at least one other coming examples take advantage of the ability to use forwarding without requiring conformance.

As with the memberwise initialization proposal, the syntax you would like to see can easily be added as syntactic sugar on top of the current proposal. I would not support that as I do not like the syntax Kotlin uses for reasons already stated, but that shouldn’t stop you from pursuing a proposal for it. Maybe a lot of people would agree with you and it would be accepted.

Matthew

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

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

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

-Dave

No. This is addressed in the proposal and the lazy collections motivating example I replied with last night. I don’t think it’s a good idea to require this.

Can you put your proposal on GitHub under your swift-evolution fork? It’s really hard to follow the current state of the proposal and the changes over time.

It is up on my fork in the automatic-protocol-forwarding branch. I haven't published the second draft yet. I want to finish the second draft before updating Github.

I shared the section on lazy collections on the list last night because Dave specifically pointed me in that direction and I am interested in his feedback on it.

The forwardee needs to implement the members of the protocol but does not need to conform. The forwarder will receive forwarding implementations of the members, but again does not need to declare conformance. Forwarding is orthogonal to conformance, just as it is today when you manually write forwarding members today.

I cannot think of a time where I wanted this pseudo-conformance. The entire purpose of forwarding, that I’ve ever wanted or seen at least, is to act as a pass-through from the outer type’s conformance to a protocol to an internal specific implementor of that protocol without having to write all of the boiler-plate code to make that happen.

So I find it confusing that you say they are orthogonal.

If you have a look at the lazy collections example maybe it will start to make more sense. I will also have other examples. If you don't want to find the post from last night, just wait until I finish the second draft. I will announce the second draft on list when it's ready.

···

Sent from my iPad

On Dec 31, 2015, at 12:02 PM, David Owens II <david@owensd.io> wrote:

On Dec 31, 2015, at 9:47 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

-David

In the second example here Forwarder does conform to P. The author of Forwarder has the flexibility to specify whether conformance is desired or not.

There are ways to handle that, including factoring the APIs of interest out of P and into a private protocol Q, then declaring the Forwardee’s conformance to Q. Now, there’s an expressivity problem with our current access control system that you can’t use an internal or private protocol to provide public API, but that should be fixed separately.

I'm not sure where Q comes into play in this specific example. The idea here is that forwarding implementations of all members of P are synthesized by the forward declaration. It is left up to Forwarder to decide whether or not to declare actual conformance to P. I am also confused by "then declaring the Forwardee’s conformance to Q" because we are discussing Forwarder's conformance here, not Forwardee’s.

Presumably both ends of the forwarding arrangement would have to conform to the same protocol, no?

No. This is addressed in the proposal and the lazy collections motivating example I replied with last night. I don’t think it’s a good idea to require this.

My only point was that if we’re going to require the forwarder to conform, we may as well require it of the forwardee. I realize that you don’t want to require either conformance.

There are independent reasons not to require it of the forwarded and the forwarder, so I disagree with "may as well".

It may be possible that eventually enough of the language limitations would be lifted that requiring conformance of both wouldn't restrict the power of a forwarding feature and as such that requirement wouldn't have any downsides other than the need to declare an additional conformance here or there.

I would prefer to no artificially restrict the power of the feature without compelling reasons to do so.

···

Sent from my iPad

On Dec 31, 2015, at 11:53 AM, Dave Abrahams <dabrahams@apple.com> wrote:

On Dec 31, 2015, at 9:47 AM, Matthew Johnson <matthew@anandabits.com> wrote:

-Dave