[Draft] open and public protocols


(Matthew Johnson) #1

Now that we’re in phase 2 I’d like to officially propose we introduce `open` protocols and require conformances to `public` protocols be inside the declaring module. Let’s use this thread for feedback on the official proposal. After a healthy round of discussion I’ll open a PR to submit it for review.

# Feature name

* Proposal: [SE-NNNN](NNNN-open-public-protocols.md)
* Authors: [Matthew Johnson](https://github.com/anandabits)
* Review Manager: TBD
* Status: **Awaiting review**

## Introduction

This proposal introduces `open protocol` and changes the meaning of `public protocol` to match the meaning of `public class` (in this case, conformances are only allowed inside the declaring module).

The pitch thread leading up to this proposal was: [consistent public access modifiers](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031653.html)

## Motivation

A general principle the Swift community has adopted for access control is that defaults should reserve maximum flexibility for a library. The ensures that any capabilities beyond mere visibility are not available unless the author of the library has explicitly declared their intent that the capabilities be made available. Finally, when it is possible to switch from one semantic to another without breaking clients (but not vice-versa) we should prefer the more forgiving (i.e. fixable) semantic as the (soft) default.

`public` is considered a "soft default" in the sense that it is the first access modifier a user will reach for when exposing a declaration outside of the module. In the case of protocols the current meaning of `public` does not meet the principle of preserving maximum flexibility for the author of the library. It allows users of the library to conform to the protocol.

There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.

Even if all the conforming types are also public there are cases where polymorphism is the preferred implementation. For example, if the set of conforming types is not expected to be fixed (despite all being inside the library) the authors may not want to have to maintain switch statements every time they need to add or remove a confroming type which would be necessary if an enum were used instead. Polymorphism allows us to avoid this, giving us the ability to add and remove conforming types within the implementation of the library without the burden of maintaining switch statements.

Aligning the access modifiers for protocols and classes allows us to specify both conformable and non-conformable protocols, provides a soft default that is consistent with the principle of (soft) defaults reserving maximum flexibility for the library, and increases the overall consistency of the language by aligning the semantics of access control for protocols and classes.

The standard library currently has at least one protocol (`MirrorPath`) that is documented as disallowing client conformances. If this proposal is adopted it is likely that `MirrorPath` would be declared `public protocol` and not `open protocol`.

Jordan Rose has indicated that the Apple frameworks also include a number of protocols documented with the intent that users do not add conformances. Perhaps an importer annotation would allow the compiler to enforce these semantics in Swift code as well.

## Proposed solution

The proposed solution is to change the meaning of `public protocol` to disallow conformances outside the declaring module and introduce `open protocol` to allow conformances outside the decalring module (equivalent to the current meaning of `public protocol`).

## Detailed design

The detailed design is relatively straightforward but there are three important wrinkles to consider.

### User refinement of public protocols

Consider the following example:

// Library module:
public protocol P {}
public class C: P {}

// User module:
protocol User: P {}
extension C: User {}

The user module is allowed to add a refinement to `P` because this does not have any impact on the impelementation of the library or its possible evolution. It simply allows the user to write code that is generic over a subset of the conforming types provided by the library.

### Public protocols with open conforming classes

Consider the following example:

public protocol P P{}
open class C: P {}

Users of this module will be able to add subclasses of `C` that have a conformance to `P`. This is allowed becuase the client of the module did not need to explicitly declare a conformance and the module has explicitly stated its intent to allow subclasses of `C` with the `open` access modifier.

### Open protocols that refine public protocols

Consider the following example:

// library module:
public protocol P {}
open protocol Q: P {}
open protocol R: P {}

// user module:
struct S: P {} // error `P` is not `open`
struct T: Q {} // ok
struct U: R {} // ok

The user module is allowed to introudce a conformance to `P`, but only indirectly by also conforming to `Q`. The meaning we have ascribed to the keywords implies that this should be allowed and it offers libraries a very wide design space from which to choose. The library is able to have types that conform directly to `P`, while placing additional requirements on user types if necessary.

## Source compatibility

This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.

1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
3. In the subsequent release `public protocol` without annotation becomes an error.
4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.

## Effect on ABI stability

I would appreciate it if others can offer input regarding this section. I believe this proposal has ABI consequences, but it's possible that it could be an additivie ABI change where the ABI for conformable protocols remains the same and we add ABI for non-conformable protocols later. If that is possible, the primary impact would be the ABI of any standard library protocols that would prefer to be non-conformable.

## Effect on API resilience

This proposal would may impact one or more protocols in the standard library, such as `MirrorPath`, which would likely choose to remain `public` rather than adopt `open`.

## Alternatives considered

The primary alternatives are to either make no change, or to add something like `closed protocol`. The issues motivating the current proposal as a better alternative than either of these options are covered in the motivation section.


(David Sweeris) #2

I don’t think we need @nonopen or warnings. IMHO, public/open should have the same semantics and syntax regardless of whether the declaration is a protocol or a concrete type (or a property?).

Other than that nit, I can’t think of a reason to oppose this. So… +1, because I like making things as consistent as possible (also because of the reasons in the motivation).

- Dave Sweeris

···

On Feb 18, 2017, at 12:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

## Source compatibility

This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.

1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
3. In the subsequent release `public protocol` without annotation becomes an error.
4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.


(Matthew Johnson) #3

## Source compatibility

This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.

1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
3. In the subsequent release `public protocol` without annotation becomes an error.
4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.

I don’t think we need @nonopen or warnings. IMHO, public/open should have the same semantics and syntax regardless of whether the declaration is a protocol or a concrete type (or a property?).

Other than that nit, I can’t think of a reason to oppose this. So… +1, because I like making things as consistent as possible (also because of the reasons in the motivation).

The purpose of using `@nonopen` for the migration is to eventually break people’s code if they don’t use the migrator and don’t annotate it. If we don’t do that the library may ship a version that unintentionally breaks their clients (by continuing to use `public` after its meaning has changed).

It’s better to break the library before it breaks any clients. That will impact many fewer developers. This can be handled automatically by the migrator and will be a relatively minor inconvenience for developers who don’t use it. That’s better than allowing an accidentally bad version of a library from shipping.

···

On Feb 18, 2017, at 3:01 PM, David Sweeris <davesweeris@mac.com> wrote:

On Feb 18, 2017, at 12:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

- Dave Sweeris


(Charles Srstka) #4

Uh, what? You can expose protocols without exposing the concrete types that conform to them. That’s kind of the whole point of protocols.

public protocol P {}

private struct S: P {}

public var p: P

reduces to this interface:

public protocol P {
}

public var p: P

The library can then assign something of type S to the “p” variable, and clients will be able to use it without having access to its concrete type.

Charles

···

On Feb 18, 2017, at 2:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.


(Adrian Zubarev) #5

Great proposal Matthew, huge thanks for pushing this forward and putting it together. I simply cannot wait for this to be implemented. :slight_smile:

+1

I spotted two typos while reading:

public protocol P P{} - one P too much
There is somewhere an erro without an r at the end

···

--
Adrian Zubarev
Sent with Airmail

Am 18. Februar 2017 um 21:42:41, Matthew Johnson via swift-evolution (swift-evolution@swift.org) schrieb:

Now that we’re in phase 2 I’d like to officially propose we introduce `open` protocols and require conformances to `public` protocols be inside the declaring module. Let’s use this thread for feedback on the official proposal. After a healthy round of discussion I’ll open a PR to submit it for review.

# Feature name

* Proposal: [SE-NNNN](NNNN-open-public-protocols.md)
* Authors: [Matthew Johnson](https://github.com/anandabits)
* Review Manager: TBD
* Status: **Awaiting review**

## Introduction

This proposal introduces `open protocol` and changes the meaning of `public protocol` to match the meaning of `public class` (in this case, conformances are only allowed inside the declaring module).

The pitch thread leading up to this proposal was: [consistent public access modifiers](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031653.html)

## Motivation

A general principle the Swift community has adopted for access control is that defaults should reserve maximum flexibility for a library. The ensures that any capabilities beyond mere visibility are not available unless the author of the library has explicitly declared their intent that the capabilities be made available. Finally, when it is possible to switch from one semantic to another without breaking clients (but not vice-versa) we should prefer the more forgiving (i.e. fixable) semantic as the (soft) default.

`public` is considered a "soft default" in the sense that it is the first access modifier a user will reach for when exposing a declaration outside of the module. In the case of protocols the current meaning of `public` does not meet the principle of preserving maximum flexibility for the author of the library. It allows users of the library to conform to the protocol.

There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.

Even if all the conforming types are also public there are cases where polymorphism is the preferred implementation. For example, if the set of conforming types is not expected to be fixed (despite all being inside the library) the authors may not want to have to maintain switch statements every time they need to add or remove a confroming type which would be necessary if an enum were used instead. Polymorphism allows us to avoid this, giving us the ability to add and remove conforming types within the implementation of the library without the burden of maintaining switch statements.

Aligning the access modifiers for protocols and classes allows us to specify both conformable and non-conformable protocols, provides a soft default that is consistent with the principle of (soft) defaults reserving maximum flexibility for the library, and increases the overall consistency of the language by aligning the semantics of access control for protocols and classes.

The standard library currently has at least one protocol (`MirrorPath`) that is documented as disallowing client conformances. If this proposal is adopted it is likely that `MirrorPath` would be declared `public protocol` and not `open protocol`.

Jordan Rose has indicated that the Apple frameworks also include a number of protocols documented with the intent that users do not add conformances. Perhaps an importer annotation would allow the compiler to enforce these semantics in Swift code as well.

## Proposed solution

The proposed solution is to change the meaning of `public protocol` to disallow conformances outside the declaring module and introduce `open protocol` to allow conformances outside the decalring module (equivalent to the current meaning of `public protocol`).

## Detailed design

The detailed design is relatively straightforward but there are three important wrinkles to consider.

### User refinement of public protocols

Consider the following example:

// Library module:
public protocol P {}
public class C: P {}

// User module:
protocol User: P {}
extension C: User {}

The user module is allowed to add a refinement to `P` because this does not have any impact on the impelementation of the library or its possible evolution. It simply allows the user to write code that is generic over a subset of the conforming types provided by the library.

### Public protocols with open conforming classes

Consider the following example:

public protocol P P{}
open class C: P {}

Users of this module will be able to add subclasses of `C` that have a conformance to `P`. This is allowed becuase the client of the module did not need to explicitly declare a conformance and the module has explicitly stated its intent to allow subclasses of `C` with the `open` access modifier.

### Open protocols that refine public protocols

Consider the following example:

// library module:
public protocol P {}
open protocol Q: P {}
open protocol R: P {}

// user module:
struct S: P {} // error `P` is not `open`
struct T: Q {} // ok
struct U: R {} // ok

The user module is allowed to introudce a conformance to `P`, but only indirectly by also conforming to `Q`. The meaning we have ascribed to the keywords implies that this should be allowed and it offers libraries a very wide design space from which to choose. The library is able to have types that conform directly to `P`, while placing additional requirements on user types if necessary.

## Source compatibility

This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.

1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
3. In the subsequent release `public protocol` without annotation becomes an error.
4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.

## Effect on ABI stability

I would appreciate it if others can offer input regarding this section. I believe this proposal has ABI consequences, but it's possible that it could be an additivie ABI change where the ABI for conformable protocols remains the same and we add ABI for non-conformable protocols later. If that is possible, the primary impact would be the ABI of any standard library protocols that would prefer to be non-conformable.

## Effect on API resilience

This proposal would may impact one or more protocols in the standard library, such as `MirrorPath`, which would likely choose to remain `public` rather than adopt `open`.

## Alternatives considered

The primary alternatives are to either make no change, or to add something like `closed protocol`. The issues motivating the current proposal as a better alternative than either of these options are covered in the motivation section.

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


(David Waite) #6

I am unsure if this feature is a good idea. Does someone have a real-world use for this which isn’t just hiding strong implementation coupling behind a protocol?

When I consume a protocol, it is under the assumption that the protocol is documented such that I would be able to work against *any* implementation of the protocol. With a closed protocol, I would have to assume that there are significant side effects, either undocumented or difficult for a third party to duplicate. To my experience, that sounds brittle.

Assuming you aren’t switching on the implementing type of a protocol (which itself can be a sign that your design isn’t properly using polymorphism), one could get this design by creating a struct with the interface desired, and passing invocations through to an internal protocol reference.

-DW

···

On Feb 18, 2017, at 1:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Now that we’re in phase 2 I’d like to officially propose we introduce `open` protocols and require conformances to `public` protocols be inside the declaring module. Let’s use this thread for feedback on the official proposal. After a healthy round of discussion I’ll open a PR to submit it for review.

# Feature name

* Proposal: [SE-NNNN](NNNN-open-public-protocols.md)
* Authors: [Matthew Johnson](https://github.com/anandabits)
* Review Manager: TBD
* Status: **Awaiting review**

## Introduction

This proposal introduces `open protocol` and changes the meaning of `public protocol` to match the meaning of `public class` (in this case, conformances are only allowed inside the declaring module).

The pitch thread leading up to this proposal was: [consistent public access modifiers](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031653.html)

## Motivation

A general principle the Swift community has adopted for access control is that defaults should reserve maximum flexibility for a library. The ensures that any capabilities beyond mere visibility are not available unless the author of the library has explicitly declared their intent that the capabilities be made available. Finally, when it is possible to switch from one semantic to another without breaking clients (but not vice-versa) we should prefer the more forgiving (i.e. fixable) semantic as the (soft) default.

`public` is considered a "soft default" in the sense that it is the first access modifier a user will reach for when exposing a declaration outside of the module. In the case of protocols the current meaning of `public` does not meet the principle of preserving maximum flexibility for the author of the library. It allows users of the library to conform to the protocol.

There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.

Even if all the conforming types are also public there are cases where polymorphism is the preferred implementation. For example, if the set of conforming types is not expected to be fixed (despite all being inside the library) the authors may not want to have to maintain switch statements every time they need to add or remove a confroming type which would be necessary if an enum were used instead. Polymorphism allows us to avoid this, giving us the ability to add and remove conforming types within the implementation of the library without the burden of maintaining switch statements.

Aligning the access modifiers for protocols and classes allows us to specify both conformable and non-conformable protocols, provides a soft default that is consistent with the principle of (soft) defaults reserving maximum flexibility for the library, and increases the overall consistency of the language by aligning the semantics of access control for protocols and classes.

The standard library currently has at least one protocol (`MirrorPath`) that is documented as disallowing client conformances. If this proposal is adopted it is likely that `MirrorPath` would be declared `public protocol` and not `open protocol`.

Jordan Rose has indicated that the Apple frameworks also include a number of protocols documented with the intent that users do not add conformances. Perhaps an importer annotation would allow the compiler to enforce these semantics in Swift code as well.

## Proposed solution

The proposed solution is to change the meaning of `public protocol` to disallow conformances outside the declaring module and introduce `open protocol` to allow conformances outside the decalring module (equivalent to the current meaning of `public protocol`).

## Detailed design

The detailed design is relatively straightforward but there are three important wrinkles to consider.

### User refinement of public protocols

Consider the following example:

// Library module:
public protocol P {}
public class C: P {}

// User module:
protocol User: P {}
extension C: User {}

The user module is allowed to add a refinement to `P` because this does not have any impact on the impelementation of the library or its possible evolution. It simply allows the user to write code that is generic over a subset of the conforming types provided by the library.

### Public protocols with open conforming classes

Consider the following example:

public protocol P P{}
open class C: P {}

Users of this module will be able to add subclasses of `C` that have a conformance to `P`. This is allowed becuase the client of the module did not need to explicitly declare a conformance and the module has explicitly stated its intent to allow subclasses of `C` with the `open` access modifier.

### Open protocols that refine public protocols

Consider the following example:

// library module:
public protocol P {}
open protocol Q: P {}
open protocol R: P {}

// user module:
struct S: P {} // error `P` is not `open`
struct T: Q {} // ok
struct U: R {} // ok

The user module is allowed to introudce a conformance to `P`, but only indirectly by also conforming to `Q`. The meaning we have ascribed to the keywords implies that this should be allowed and it offers libraries a very wide design space from which to choose. The library is able to have types that conform directly to `P`, while placing additional requirements on user types if necessary.

## Source compatibility

This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.

1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
3. In the subsequent release `public protocol` without annotation becomes an error.
4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.

## Effect on ABI stability

I would appreciate it if others can offer input regarding this section. I believe this proposal has ABI consequences, but it's possible that it could be an additivie ABI change where the ABI for conformable protocols remains the same and we add ABI for non-conformable protocols later. If that is possible, the primary impact would be the ABI of any standard library protocols that would prefer to be non-conformable.

## Effect on API resilience

This proposal would may impact one or more protocols in the standard library, such as `MirrorPath`, which would likely choose to remain `public` rather than adopt `open`.

## Alternatives considered

The primary alternatives are to either make no change, or to add something like `closed protocol`. The issues motivating the current proposal as a better alternative than either of these options are covered in the motivation section.

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


(David Sweeris) #7

Do we need it for types? "@nonopen public class Foo {...}"?

(Serious question... I don't recall if we did this phased thing for open vs public types)

- Dave Sweeris

···

On Feb 18, 2017, at 13:12, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 18, 2017, at 3:01 PM, David Sweeris <davesweeris@mac.com> wrote:

On Feb 18, 2017, at 12:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

## Source compatibility

This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.

1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
3. In the subsequent release `public protocol` without annotation becomes an error.
4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.

I don’t think we need @nonopen or warnings. IMHO, public/open should have the same semantics and syntax regardless of whether the declaration is a protocol or a concrete type (or a property?).

Other than that nit, I can’t think of a reason to oppose this. So… +1, because I like making things as consistent as possible (also because of the reasons in the motivation).

The purpose of using `@nonopen` for the migration is to eventually break people’s code if they don’t use the migrator and don’t annotate it. If we don’t do that the library may ship a version that unintentionally breaks their clients (by continuing to use `public` after its meaning has changed).

It’s better to break the library before it breaks any clients. That will impact many fewer developers. This can be handled automatically by the migrator and will be a relatively minor inconvenience for developers who don’t use it. That’s better than allowing an accidentally bad version of a library from shipping.


(Matthew Johnson) #8

Sorry, this part was not stated as clearly as it should have been. What intends to say is that the library may wish to not only hide the concrete types, but also control the set of types which may conform to the protocol (or be associated values of the enum) such that users cannot add to the set of types. I will update the document to more clearly describe this use case. Thanks for catching this!

···

On Feb 18, 2017, at 3:43 PM, Charles Srstka <cocoadev@charlessoft.com> wrote:

On Feb 18, 2017, at 2:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.

Uh, what? You can expose protocols without exposing the concrete types that conform to them. That’s kind of the whole point of protocols.

public protocol P {}

private struct S: P {}

public var p: P

reduces to this interface:

public protocol P {
}

public var p: P

The library can then assign something of type S to the “p” variable, and clients will be able to use it without having access to its concrete type.

Charles


(Matthew Johnson) #9

## Source compatibility

This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.

1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
3. In the subsequent release `public protocol` without annotation becomes an error.
4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.

I don’t think we need @nonopen or warnings. IMHO, public/open should have the same semantics and syntax regardless of whether the declaration is a protocol or a concrete type (or a property?).

Other than that nit, I can’t think of a reason to oppose this. So… +1, because I like making things as consistent as possible (also because of the reasons in the motivation).

The purpose of using `@nonopen` for the migration is to eventually break people’s code if they don’t use the migrator and don’t annotate it. If we don’t do that the library may ship a version that unintentionally breaks their clients (by continuing to use `public` after its meaning has changed).

It’s better to break the library before it breaks any clients. That will impact many fewer developers. This can be handled automatically by the migrator and will be a relatively minor inconvenience for developers who don’t use it. That’s better than allowing an accidentally bad version of a library from shipping.

Do we need it for types? "@nonopen public class Foo {...}"?

(Serious question... I don't recall if we did this phased thing for open vs public types)

No, because we already introduced that in Swift 3. Swift 4 has a higher bar for breaking changes. If the core team is willing to accept the proposal without a staged migration strategy I would not object to that. But I believe it’s best for breaking proposals to present a staged migration strategy for the core team to consider. That’s what this is. I wouldn’t want lack of a staged migration strategy to sink the proposal.

···

On Feb 18, 2017, at 3:52 PM, David Sweeris <davesweeris@mac.com> wrote:

On Feb 18, 2017, at 13:12, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 18, 2017, at 3:01 PM, David Sweeris <davesweeris@mac.com> wrote:

On Feb 18, 2017, at 12:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

- Dave Sweeris


(Adrian Zubarev) #10

Protocols solve way more problems than this. Sometimes they are used as backdoors to existing APIs as well. It’s simply an implementation artifact that such a protocol might end up public, but there is no point to allow the client to conform to it.

I also showed a concrete example in the discussion thread where I needed a public-but-not-open protocol really badly. :wink:

···

--
Adrian Zubarev
Sent with Airmail

Am 18. Februar 2017 um 22:45:00, Charles Srstka via swift-evolution (swift-evolution@swift.org) schrieb:

On Feb 18, 2017, at 2:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.

Uh, what? You can expose protocols without exposing the concrete types that conform to them. That’s kind of the whole point of protocols.

public protocol P {}

private struct S: P {}

public var p: P

reduces to this interface:

public protocol P {
}

public var p: P

The library can then assign something of type S to the “p” variable, and clients will be able to use it without having access to its concrete type.

Charles

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


(Matthew Johnson) #11

I am unsure if this feature is a good idea. Does someone have a real-world use for this which isn’t just hiding strong implementation coupling behind a protocol?

Yes. If this proposal moves ahead we can implement an API that takes a hereogenous collection with implicit conversion for several different concrete types while ensuring that all elements are of a type provided by the library:

public protocol Foo {}
public struct One: Foo {}
public struct Two: Foo {}
// other conforming types in the library

func takesFoos(_ foos: [Foo]) {}

// in user code:
takesFoos([One(), One(), Two(), One()])

This use case could be subsumed by enums if we implement some of the ideas in my value subtyping manifesto but those are for the future and may or may not be accepted. This is a relatively small change that can e made in Swift 4.

···

Sent from my iPad

On Feb 19, 2017, at 12:58 AM, David Waite <david@alkaline-solutions.com> wrote:

When I consume a protocol, it is under the assumption that the protocol is documented such that I would be able to work against *any* implementation of the protocol. With a closed protocol, I would have to assume that there are significant side effects, either undocumented or difficult for a third party to duplicate. To my experience, that sounds brittle.

Assuming you aren’t switching on the implementing type of a protocol (which itself can be a sign that your design isn’t properly using polymorphism), one could get this design by creating a struct with the interface desired, and passing invocations through to an internal protocol reference.

-DW

On Feb 18, 2017, at 1:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Now that we’re in phase 2 I’d like to officially propose we introduce `open` protocols and require conformances to `public` protocols be inside the declaring module. Let’s use this thread for feedback on the official proposal. After a healthy round of discussion I’ll open a PR to submit it for review.

# Feature name

* Proposal: [SE-NNNN](NNNN-open-public-protocols.md)
* Authors: [Matthew Johnson](https://github.com/anandabits)
* Review Manager: TBD
* Status: **Awaiting review**

## Introduction

This proposal introduces `open protocol` and changes the meaning of `public protocol` to match the meaning of `public class` (in this case, conformances are only allowed inside the declaring module).

The pitch thread leading up to this proposal was: [consistent public access modifiers](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031653.html)

## Motivation

A general principle the Swift community has adopted for access control is that defaults should reserve maximum flexibility for a library. The ensures that any capabilities beyond mere visibility are not available unless the author of the library has explicitly declared their intent that the capabilities be made available. Finally, when it is possible to switch from one semantic to another without breaking clients (but not vice-versa) we should prefer the more forgiving (i.e. fixable) semantic as the (soft) default.

`public` is considered a "soft default" in the sense that it is the first access modifier a user will reach for when exposing a declaration outside of the module. In the case of protocols the current meaning of `public` does not meet the principle of preserving maximum flexibility for the author of the library. It allows users of the library to conform to the protocol.

There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.

Even if all the conforming types are also public there are cases where polymorphism is the preferred implementation. For example, if the set of conforming types is not expected to be fixed (despite all being inside the library) the authors may not want to have to maintain switch statements every time they need to add or remove a confroming type which would be necessary if an enum were used instead. Polymorphism allows us to avoid this, giving us the ability to add and remove conforming types within the implementation of the library without the burden of maintaining switch statements.

Aligning the access modifiers for protocols and classes allows us to specify both conformable and non-conformable protocols, provides a soft default that is consistent with the principle of (soft) defaults reserving maximum flexibility for the library, and increases the overall consistency of the language by aligning the semantics of access control for protocols and classes.

The standard library currently has at least one protocol (`MirrorPath`) that is documented as disallowing client conformances. If this proposal is adopted it is likely that `MirrorPath` would be declared `public protocol` and not `open protocol`.

Jordan Rose has indicated that the Apple frameworks also include a number of protocols documented with the intent that users do not add conformances. Perhaps an importer annotation would allow the compiler to enforce these semantics in Swift code as well.

## Proposed solution

The proposed solution is to change the meaning of `public protocol` to disallow conformances outside the declaring module and introduce `open protocol` to allow conformances outside the decalring module (equivalent to the current meaning of `public protocol`).

## Detailed design

The detailed design is relatively straightforward but there are three important wrinkles to consider.

### User refinement of public protocols

Consider the following example:

// Library module:
public protocol P {}
public class C: P {}

// User module:
protocol User: P {}
extension C: User {}

The user module is allowed to add a refinement to `P` because this does not have any impact on the impelementation of the library or its possible evolution. It simply allows the user to write code that is generic over a subset of the conforming types provided by the library.

### Public protocols with open conforming classes

Consider the following example:

public protocol P P{}
open class C: P {}

Users of this module will be able to add subclasses of `C` that have a conformance to `P`. This is allowed becuase the client of the module did not need to explicitly declare a conformance and the module has explicitly stated its intent to allow subclasses of `C` with the `open` access modifier.

### Open protocols that refine public protocols

Consider the following example:

// library module:
public protocol P {}
open protocol Q: P {}
open protocol R: P {}

// user module:
struct S: P {} // error `P` is not `open`
struct T: Q {} // ok
struct U: R {} // ok

The user module is allowed to introudce a conformance to `P`, but only indirectly by also conforming to `Q`. The meaning we have ascribed to the keywords implies that this should be allowed and it offers libraries a very wide design space from which to choose. The library is able to have types that conform directly to `P`, while placing additional requirements on user types if necessary.

## Source compatibility

This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.

1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
3. In the subsequent release `public protocol` without annotation becomes an error.
4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.

## Effect on ABI stability

I would appreciate it if others can offer input regarding this section. I believe this proposal has ABI consequences, but it's possible that it could be an additivie ABI change where the ABI for conformable protocols remains the same and we add ABI for non-conformable protocols later. If that is possible, the primary impact would be the ABI of any standard library protocols that would prefer to be non-conformable.

## Effect on API resilience

This proposal would may impact one or more protocols in the standard library, such as `MirrorPath`, which would likely choose to remain `public` rather than adopt `open`.

## Alternatives considered

The primary alternatives are to either make no change, or to add something like `closed protocol`. The issues motivating the current proposal as a better alternative than either of these options are covered in the motivation section.

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


(Adrian Zubarev) #12

If you haven’t followed the other thread Matthew previously opened than you have missed the example I showed there.

Here it is again:

public protocol SubscriptParameterType {
      
    // This property was needed to prevent the client from breaking
    // the library by conforming to the protocol, but I'd like to
    // keep it invisible for the client, or even better prevent the
    // client from conforming to the protocol.
    var parameter: Document.SubscriptParameter { get }
}

extension Document {
      
    public enum SubscriptParameter {
              
        case string(String)
        case integer(Int)
    }
}

extension String : SubscriptParameterType {
      
    public var parameter: Document.SubscriptParameter {
          
        return .string(self)
    }
}

extension Int : SubscriptParameterType {
      
    public var parameter: Document.SubscriptParameter {
          
        return .integer(self)
    }
}

// Somewhere inside the `Document` type
public subscript(firstKey: String, parameters: SubscriptParameterType...) -> Value? { … }
The absence of closed protocols forced me to create a special requirement on that protocol to prevent the client from conforming to that protocol and passing instances of other types my API wouldn’t want to deal with. That creates unnecessary copies and I need to unpack the enum payload to find out which type the user passed. Instead I could simply close the protocol, wouldn’t need the requirement to exist and I could simply cast the type to String or Int when needed.

That implementation enables more safe queries of my Document type like

document["key1", intIndexInstance, stringKeyInstance, 10, "key"]

rather than

document["key1/\(intIndexInstance)/\(stringKeyInstance)/10/key"].

Here is a list of hidden and semi-hidden protocols from the standard library that could be closed. Formatted version: https://gist.github.com/DevAndArtist/168c800d784829be536c407311953ab7

Path Protocol
/swift/stdlib/public/core/AnyHashable.swift:16 _HasCustomAnyHashableRepresentation
/swift/stdlib/public/core/BidirectionalCollection.swift:21 _BidirectionalIndexable
/swift/stdlib/public/core/BridgeObjectiveC.swift:19 _ObjectiveCBridgeable
/swift/stdlib/public/core/Collection.swift:20 _IndexableBase
/swift/stdlib/public/core/Collection.swift:176 _Indexable
/swift/stdlib/public/core/CompilerProtocols.swift:193 _ExpressibleByBuiltinIntegerLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:240 _ExpressibleByBuiltinFloatLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:283 _ExpressibleByBuiltinBooleanLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:316 _ExpressibleByBuiltinUnicodeScalarLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:350 _ExpressibleByBuiltinExtendedGraphemeClusterLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:398 _ExpressibleByBuiltinStringLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:407 _ExpressibleByBuiltinUTF16StringLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:670 _ExpressibleByStringInterpolation
/swift/stdlib/public/core/CompilerProtocols.swift:709 _ExpressibleByColorLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:720 _ExpressibleByImageLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:730 _ExpressibleByFileReferenceLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:750 _DestructorSafeContainer
/swift/stdlib/public/core/FixedPoint.swift.gyb:53 _Integer
/swift/stdlib/public/core/FixedPoint.swift.gyb:70 _SignedInteger
/swift/stdlib/public/core/FixedPoint.swift.gyb:108 _DisallowMixedSignArithmetic
/swift/stdlib/public/core/Hashable.swift:16 _Hashable
/swift/stdlib/public/core/Index.swift:16 _Incrementable
/swift/stdlib/public/core/IntegerArithmetic.swift.gyb:33 _IntegerArithmetic
/swift/stdlib/public/core/Mirror.swift:721 _DefaultCustomPlaygroundQuickLookable
/swift/stdlib/public/core/MutableCollection.swift:20 _MutableIndexable
/swift/stdlib/public/core/NewtypeWrapper.swift.gyb:16 _SwiftNewtypeWrapper
/swift/stdlib/public/core/Pointer.swift:16 _Pointer
/swift/stdlib/public/core/RandomAccessCollection.swift:20 _RandomAccessIndexable
/swift/stdlib/public/core/RangeReplaceableCollection.swift.gyb:27 _RangeReplaceableIndexable
/swift/stdlib/public/core/ReflectionLegacy.swift:41 _Mirror
/swift/stdlib/public/core/ShadowProtocols.swift:27 _ShadowProtocol
/swift/stdlib/public/core/ShadowProtocols.swift:31 _NSFastEnumeration
/swift/stdlib/public/core/ShadowProtocols.swift:41 _NSEnumerator
/swift/stdlib/public/core/ShadowProtocols.swift:51 _NSCopying
/swift/stdlib/public/core/ShadowProtocols.swift:61 _NSArrayCore
/swift/stdlib/public/core/ShadowProtocols.swift:83 _NSDictionaryCore
/swift/stdlib/public/core/ShadowProtocols.swift:125 _NSDictionary
/swift/stdlib/public/core/ShadowProtocols.swift:137 _NSSetCore
/swift/stdlib/public/core/ShadowProtocols.swift:171 _NSSet
/swift/stdlib/public/core/ShadowProtocols.swift:177 _NSNumber
/swift/stdlib/public/core/ShadowProtocols.swift:187 _NSArrayCore
/swift/stdlib/public/core/ShadowProtocols.swift:188 _NSDictionaryCore
/swift/stdlib/public/core/ShadowProtocols.swift:189 _NSSetCore
/swift/stdlib/public/core/StringBridge.swift:194 _NSStringCore
/swift/stdlib/public/SDK/Foundation/NSError.swift:353 _ObjectiveCBridgeableError
/swift/stdlib/public/SDK/Foundation/NSError.swift:379 __BridgedNSError
/swift/stdlib/public/SDK/Foundation/NSError.swift:446 _BridgedNSError
/swift/stdlib/public/SDK/Foundation/NSError.swift:456 _BridgedStoredNSError
/swift/stdlib/public/SDK/Foundation/NSError.swift:564 _ErrorCodeProtocol

···

--
Adrian Zubarev
Sent with Airmail

Am 19. Februar 2017 um 07:59:45, David Waite via swift-evolution (swift-evolution@swift.org) schrieb:

I am unsure if this feature is a good idea. Does someone have a real-world use for this which isn’t just hiding strong implementation coupling behind a protocol?

When I consume a protocol, it is under the assumption that the protocol is documented such that I would be able to work against *any* implementation of the protocol. With a closed protocol, I would have to assume that there are significant side effects, either undocumented or difficult for a third party to duplicate. To my experience, that sounds brittle.

Assuming you aren’t switching on the implementing type of a protocol (which itself can be a sign that your design isn’t properly using polymorphism), one could get this design by creating a struct with the interface desired, and passing invocations through to an internal protocol reference.

-DW

On Feb 18, 2017, at 1:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Now that we’re in phase 2 I’d like to officially propose we introduce `open` protocols and require conformances to `public` protocols be inside the declaring module. Let’s use this thread for feedback on the official proposal. After a healthy round of discussion I’ll open a PR to submit it for review.

# Feature name

* Proposal: [SE-NNNN](NNNN-open-public-protocols.md)
* Authors: [Matthew Johnson](https://github.com/anandabits)
* Review Manager: TBD
* Status: **Awaiting review**

## Introduction

This proposal introduces `open protocol` and changes the meaning of `public protocol` to match the meaning of `public class` (in this case, conformances are only allowed inside the declaring module).

The pitch thread leading up to this proposal was: [consistent public access modifiers](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031653.html)

## Motivation

A general principle the Swift community has adopted for access control is that defaults should reserve maximum flexibility for a library. The ensures that any capabilities beyond mere visibility are not available unless the author of the library has explicitly declared their intent that the capabilities be made available. Finally, when it is possible to switch from one semantic to another without breaking clients (but not vice-versa) we should prefer the more forgiving (i.e. fixable) semantic as the (soft) default.

`public` is considered a "soft default" in the sense that it is the first access modifier a user will reach for when exposing a declaration outside of the module. In the case of protocols the current meaning of `public` does not meet the principle of preserving maximum flexibility for the author of the library. It allows users of the library to conform to the protocol.

There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.

Even if all the conforming types are also public there are cases where polymorphism is the preferred implementation. For example, if the set of conforming types is not expected to be fixed (despite all being inside the library) the authors may not want to have to maintain switch statements every time they need to add or remove a confroming type which would be necessary if an enum were used instead. Polymorphism allows us to avoid this, giving us the ability to add and remove conforming types within the implementation of the library without the burden of maintaining switch statements.

Aligning the access modifiers for protocols and classes allows us to specify both conformable and non-conformable protocols, provides a soft default that is consistent with the principle of (soft) defaults reserving maximum flexibility for the library, and increases the overall consistency of the language by aligning the semantics of access control for protocols and classes.

The standard library currently has at least one protocol (`MirrorPath`) that is documented as disallowing client conformances. If this proposal is adopted it is likely that `MirrorPath` would be declared `public protocol` and not `open protocol`.

Jordan Rose has indicated that the Apple frameworks also include a number of protocols documented with the intent that users do not add conformances. Perhaps an importer annotation would allow the compiler to enforce these semantics in Swift code as well.

## Proposed solution

The proposed solution is to change the meaning of `public protocol` to disallow conformances outside the declaring module and introduce `open protocol` to allow conformances outside the decalring module (equivalent to the current meaning of `public protocol`).

## Detailed design

The detailed design is relatively straightforward but there are three important wrinkles to consider.

### User refinement of public protocols

Consider the following example:

// Library module:
public protocol P {}
public class C: P {}

// User module:
protocol User: P {}
extension C: User {}

The user module is allowed to add a refinement to `P` because this does not have any impact on the impelementation of the library or its possible evolution. It simply allows the user to write code that is generic over a subset of the conforming types provided by the library.

### Public protocols with open conforming classes

Consider the following example:

public protocol P P{}
open class C: P {}

Users of this module will be able to add subclasses of `C` that have a conformance to `P`. This is allowed becuase the client of the module did not need to explicitly declare a conformance and the module has explicitly stated its intent to allow subclasses of `C` with the `open` access modifier.

### Open protocols that refine public protocols

Consider the following example:

// library module:
public protocol P {}
open protocol Q: P {}
open protocol R: P {}

// user module:
struct S: P {} // error `P` is not `open`
struct T: Q {} // ok
struct U: R {} // ok

The user module is allowed to introudce a conformance to `P`, but only indirectly by also conforming to `Q`. The meaning we have ascribed to the keywords implies that this should be allowed and it offers libraries a very wide design space from which to choose. The library is able to have types that conform directly to `P`, while placing additional requirements on user types if necessary.

## Source compatibility

This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.

1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
3. In the subsequent release `public protocol` without annotation becomes an error.
4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.

## Effect on ABI stability

I would appreciate it if others can offer input regarding this section. I believe this proposal has ABI consequences, but it's possible that it could be an additivie ABI change where the ABI for conformable protocols remains the same and we add ABI for non-conformable protocols later. If that is possible, the primary impact would be the ABI of any standard library protocols that would prefer to be non-conformable.

## Effect on API resilience

This proposal would may impact one or more protocols in the standard library, such as `MirrorPath`, which would likely choose to remain `public` rather than adopt `open`.

## Alternatives considered

The primary alternatives are to either make no change, or to add something like `closed protocol`. The issues motivating the current proposal as a better alternative than either of these options are covered in the motivation section.

_______________________________________________
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


(Brent Royal-Gordon) #13

Strong coupling is sometimes inevitable.

In a previous thread, I brought up an example of a place I would use this feature: Wrapping the SQLite APIs. For instance:

  public protocol SQLiteValue {
    init(statement: SQLiteStatement, columnAt index: Int) throws
    func bind(to statement: SQLiteStatement, at index: Int) throws
  }
  extension Int: SQLiteValue {
    public init(statement: SQLiteStatement, columnAt index: Int) throws {
      self = sqlite3_column_int(statement.stmt, index)
    }
    public func bind(to statement: SQLiteStatement, at index: Int) throws {
      try throwIfNotOK(
        sqlite3_bind_int64(statement.stmt, index, self)
      )
    }
  }
  extension Double: SQLiteValue {…}
  extension Data: SQLiteValue {…}
  extension String: SQLiteValue {…}
  extension Optional: SQLiteValue where Wrapped: SQLiteValue {…}

This is a case of your hated "strong implementation coupling". But the coupling is to a library that ultimately writes data to disk in a portable format. Strong coupling here is inevitable.

What is the purpose of permitting outside conformances to `SQLiteValue`? There is no useful way to conform to `SQLiteValue`; the underlying library supports certain types, and I've implemented support for those types. Allowing outside conformances can only mislead people into fruitlessly trying to conform their types, not realizing that the calls they need simply aren't exposed.

Moreover, exposing these details unnecessarily freezes the design of `SQLiteValue`. If I want to change the design of this parameter handling in a future version, well, too bad, the API is public, I'm stuck. *For an API I don't intend anyone to conform to publicly in the first place.* That kind of sucks, doesn't it?

···

On Feb 18, 2017, at 10:58 PM, David Waite via swift-evolution <swift-evolution@swift.org> wrote:

I am unsure if this feature is a good idea. Does someone have a real-world use for this which isn’t just hiding strong implementation coupling behind a protocol?

--
Brent Royal-Gordon
Architechies


(David Sweeris) #14

Ah! Ok, I can see that.

Well, for whatever my opinion is worth, I'd prefer we left that part out. But I'd rather leave it in than for the proposal to get rejected, though.

- Dave Sweeris

···

On Feb 18, 2017, at 14:28, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 18, 2017, at 3:52 PM, David Sweeris <davesweeris@mac.com> wrote:

On Feb 18, 2017, at 13:12, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 18, 2017, at 3:01 PM, David Sweeris <davesweeris@mac.com> wrote:

On Feb 18, 2017, at 12:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

## Source compatibility

This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.

1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
3. In the subsequent release `public protocol` without annotation becomes an error.
4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.

I don’t think we need @nonopen or warnings. IMHO, public/open should have the same semantics and syntax regardless of whether the declaration is a protocol or a concrete type (or a property?).

Other than that nit, I can’t think of a reason to oppose this. So… +1, because I like making things as consistent as possible (also because of the reasons in the motivation).

The purpose of using `@nonopen` for the migration is to eventually break people’s code if they don’t use the migrator and don’t annotate it. If we don’t do that the library may ship a version that unintentionally breaks their clients (by continuing to use `public` after its meaning has changed).

It’s better to break the library before it breaks any clients. That will impact many fewer developers. This can be handled automatically by the migrator and will be a relatively minor inconvenience for developers who don’t use it. That’s better than allowing an accidentally bad version of a library from shipping.

Do we need it for types? "@nonopen public class Foo {...}"?

(Serious question... I don't recall if we did this phased thing for open vs public types)

No, because we already introduced that in Swift 3. Swift 4 has a higher bar for breaking changes. If the core team is willing to accept the proposal without a staged migration strategy I would not object to that. But I believe it’s best for breaking proposals to present a staged migration strategy for the core team to consider. That’s what this is. I wouldn’t want lack of a staged migration strategy to sink the proposal.


(Xiaodi Wu) #15

There's no way to make a source-breaking change non-breaking. A migration
path that meanders through several different designs just increases the
aggregate pain over multiple releases. The proposed migration path has the
great drawback that, even once approved, no person would actually be
allowed to write code that would compile under the final accepted design
until several versions in the future.

Either this issue about `open` protocols is so important that it meets the
bar for a source-breaking change in Swift 4 (see Ted Kremenek's post about
the very stringent criteria), and thus it merits a single discrete break in
source compatibility between Swift versions, or it is not sufficiently
important.

···

On Sat, Feb 18, 2017 at 4:44 PM, David Sweeris via swift-evolution < swift-evolution@swift.org> wrote:

> On Feb 18, 2017, at 14:28, Matthew Johnson <matthew@anandabits.com> > wrote:
>
>
>> On Feb 18, 2017, at 3:52 PM, David Sweeris <davesweeris@mac.com> wrote:
>>
>>
>>> On Feb 18, 2017, at 13:12, Matthew Johnson <matthew@anandabits.com> > wrote:
>>>
>>>
>>>> On Feb 18, 2017, at 3:01 PM, David Sweeris <davesweeris@mac.com> > wrote:
>>>>
>>>>
>>>>> On Feb 18, 2017, at 12:41 PM, Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:
>>>>>
>>>>> ## Source compatibility
>>>>>
>>>>> This proposal breaks source compatibility, but in a way that allows
for a simple mechanical migration. A multi-release stratgegy will be used
to roll out this proposal to provide maximum possible source compatibility
from one release to the next.
>>>>>
>>>>> 1. In Swift 4, introduce the `open` keyword and the `@nonopen`
attribute (which can be applied to `public protocol` to give it the new
semantics of `public`).
>>>>> 2. In Swift 4 (or 4.1 if necessary) start warning for `public
protocol` with no annotation.
>>>>> 3. In the subsequent release `public protocol` without annotation
becomes an error.
>>>>> 4. In the subsequent relase `public protocol` without annotation
takes on the new semantics.
>>>>> 5. `@nonopen` becomes a warning, and evenutally an erro as soon as
we are comfortable making those changes.
>>>>
>>>> I don’t think we need @nonopen or warnings. IMHO, public/open should
have the same semantics and syntax regardless of whether the declaration is
a protocol or a concrete type (or a property?).
>>>>
>>>> Other than that nit, I can’t think of a reason to oppose this. So…
+1, because I like making things as consistent as possible (also because of
the reasons in the motivation).
>>>
>>> The purpose of using `@nonopen` for the migration is to eventually
break people’s code if they don’t use the migrator and don’t annotate it.
If we don’t do that the library may ship a version that unintentionally
breaks their clients (by continuing to use `public` after its meaning has
changed).
>>>
>>> It’s better to break the library before it breaks any clients. That
will impact many fewer developers. This can be handled automatically by
the migrator and will be a relatively minor inconvenience for developers
who don’t use it. That’s better than allowing an accidentally bad version
of a library from shipping.
>>
>> Do we need it for types? "@nonopen public class Foo {...}"?
>>
>> (Serious question... I don't recall if we did this phased thing for
open vs public types)
>
> No, because we already introduced that in Swift 3. Swift 4 has a higher
bar for breaking changes. If the core team is willing to accept the
proposal without a staged migration strategy I would not object to that.
But I believe it’s best for breaking proposals to present a staged
migration strategy for the core team to consider. That’s what this is. I
wouldn’t want lack of a staged migration strategy to sink the proposal.

Ah! Ok, I can see that.

Well, for whatever my opinion is worth, I'd prefer we left that part out.
But I'd rather leave it in than for the proposal to get rejected, though.

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


(David Waite) #16

Just FYI, I solved this issue in my own library (which included a json jpointer implementation) via:

public enum SubscriptParameter {
  case string(String)
  case int(Int)
}

extension SubscriptParameter : ExpressibleByIntegerLiteral {
  public init(integerLiteral value: Int) {
    self = .int(value)
  }
}

extension SubscriptParameter : ExpressibleByStringLiteral {
  public init(stringLiteral value: String) {
    self = .string(value)
  }
  public init(extendedGraphemeClusterLiteral value: String) {
    self.init(stringLiteral: value)
  }
  public init(unicodeScalarLiteral value: String) {
    self.init(stringLiteral: value)
  }
}

extension SubscriptParameter : CustomStringConvertible {
  public var description: String {
    switch self {
    case .string(let str):
      return "\"\(str)\""
    case .int(let i):
      return String(i)
    }
  }
}

func debug(_ path:SubscriptParameter...) {
  print("path is \(path)")
}

debug(1, "foo", 2, "bar”) // path is [1, “foo”, 2, “bar”]

···

On Feb 19, 2017, at 1:14 AM, Adrian Zubarev <adrian.zubarev@devandartist.com> wrote:

If you haven’t followed the other thread Matthew previously opened than you have missed the example I showed there.

Here it is again:

public protocol SubscriptParameterType {
      
    // This property was needed to prevent the client from breaking
    // the library by conforming to the protocol, but I'd like to
    // keep it invisible for the client, or even better prevent the
    // client from conforming to the protocol.
    var parameter: Document.SubscriptParameter { get }
}

extension Document {
      
    public enum SubscriptParameter {
              
        case string(String)
        case integer(Int)
    }
}

extension String : SubscriptParameterType {
      
    public var parameter: Document.SubscriptParameter {
          
        return .string(self)
    }
}

extension Int : SubscriptParameterType {
      
    public var parameter: Document.SubscriptParameter {
          
        return .integer(self)
    }
}

// Somewhere inside the `Document` type
public subscript(firstKey: String, parameters: SubscriptParameterType...) -> Value? { … }
The absence of closed protocols forced me to create a special requirement on that protocol to prevent the client from conforming to that protocol and passing instances of other types my API wouldn’t want to deal with. That creates unnecessary copies and I need to unpack the enum payload to find out which type the user passed. Instead I could simply close the protocol, wouldn’t need the requirement to exist and I could simply cast the type to String or Int when needed.

That implementation enables more safe queries of my Document type like

document["key1", intIndexInstance, stringKeyInstance, 10, "key"]

rather than

document["key1/\(intIndexInstance)/\(stringKeyInstance)/10/key"].

Here is a list of hidden and semi-hidden protocols from the standard library that could be closed. Formatted version: https://gist.github.com/DevAndArtist/168c800d784829be536c407311953ab7
Path Protocol
/swift/stdlib/public/core/AnyHashable.swift:16 _HasCustomAnyHashableRepresentation
/swift/stdlib/public/core/BidirectionalCollection.swift:21 _BidirectionalIndexable
/swift/stdlib/public/core/BridgeObjectiveC.swift:19 _ObjectiveCBridgeable
/swift/stdlib/public/core/Collection.swift:20 _IndexableBase
/swift/stdlib/public/core/Collection.swift:176 _Indexable
/swift/stdlib/public/core/CompilerProtocols.swift:193 _ExpressibleByBuiltinIntegerLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:240 _ExpressibleByBuiltinFloatLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:283 _ExpressibleByBuiltinBooleanLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:316 _ExpressibleByBuiltinUnicodeScalarLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:350 _ExpressibleByBuiltinExtendedGraphemeClusterLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:398 _ExpressibleByBuiltinStringLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:407 _ExpressibleByBuiltinUTF16StringLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:670 _ExpressibleByStringInterpolation
/swift/stdlib/public/core/CompilerProtocols.swift:709 _ExpressibleByColorLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:720 _ExpressibleByImageLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:730 _ExpressibleByFileReferenceLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:750 _DestructorSafeContainer
/swift/stdlib/public/core/FixedPoint.swift.gyb:53 _Integer
/swift/stdlib/public/core/FixedPoint.swift.gyb:70 _SignedInteger
/swift/stdlib/public/core/FixedPoint.swift.gyb:108 _DisallowMixedSignArithmetic
/swift/stdlib/public/core/Hashable.swift:16 _Hashable
/swift/stdlib/public/core/Index.swift:16 _Incrementable
/swift/stdlib/public/core/IntegerArithmetic.swift.gyb:33 _IntegerArithmetic
/swift/stdlib/public/core/Mirror.swift:721 _DefaultCustomPlaygroundQuickLookable
/swift/stdlib/public/core/MutableCollection.swift:20 _MutableIndexable
/swift/stdlib/public/core/NewtypeWrapper.swift.gyb:16 _SwiftNewtypeWrapper
/swift/stdlib/public/core/Pointer.swift:16 _Pointer
/swift/stdlib/public/core/RandomAccessCollection.swift:20 _RandomAccessIndexable
/swift/stdlib/public/core/RangeReplaceableCollection.swift.gyb:27 _RangeReplaceableIndexable
/swift/stdlib/public/core/ReflectionLegacy.swift:41 _Mirror
/swift/stdlib/public/core/ShadowProtocols.swift:27 _ShadowProtocol
/swift/stdlib/public/core/ShadowProtocols.swift:31 _NSFastEnumeration
/swift/stdlib/public/core/ShadowProtocols.swift:41 _NSEnumerator
/swift/stdlib/public/core/ShadowProtocols.swift:51 _NSCopying
/swift/stdlib/public/core/ShadowProtocols.swift:61 _NSArrayCore
/swift/stdlib/public/core/ShadowProtocols.swift:83 _NSDictionaryCore
/swift/stdlib/public/core/ShadowProtocols.swift:125 _NSDictionary
/swift/stdlib/public/core/ShadowProtocols.swift:137 _NSSetCore
/swift/stdlib/public/core/ShadowProtocols.swift:171 _NSSet
/swift/stdlib/public/core/ShadowProtocols.swift:177 _NSNumber
/swift/stdlib/public/core/ShadowProtocols.swift:187 _NSArrayCore
/swift/stdlib/public/core/ShadowProtocols.swift:188 _NSDictionaryCore
/swift/stdlib/public/core/ShadowProtocols.swift:189 _NSSetCore
/swift/stdlib/public/core/StringBridge.swift:194 _NSStringCore
/swift/stdlib/public/SDK/Foundation/NSError.swift:353 _ObjectiveCBridgeableError
/swift/stdlib/public/SDK/Foundation/NSError.swift:379 __BridgedNSError
/swift/stdlib/public/SDK/Foundation/NSError.swift:446 _BridgedNSError
/swift/stdlib/public/SDK/Foundation/NSError.swift:456 _BridgedStoredNSError
/swift/stdlib/public/SDK/Foundation/NSError.swift:564 _ErrorCodeProtocol

--
Adrian Zubarev
Sent with Airmail

Am 19. Februar 2017 um 07:59:45, David Waite via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

I am unsure if this feature is a good idea. Does someone have a real-world use for this which isn’t just hiding strong implementation coupling behind a protocol?

When I consume a protocol, it is under the assumption that the protocol is documented such that I would be able to work against *any* implementation of the protocol. With a closed protocol, I would have to assume that there are significant side effects, either undocumented or difficult for a third party to duplicate. To my experience, that sounds brittle.

Assuming you aren’t switching on the implementing type of a protocol (which itself can be a sign that your design isn’t properly using polymorphism), one could get this design by creating a struct with the interface desired, and passing invocations through to an internal protocol reference.

-DW

> On Feb 18, 2017, at 1:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:
>
> Now that we’re in phase 2 I’d like to officially propose we introduce `open` protocols and require conformances to `public` protocols be inside the declaring module. Let’s use this thread for feedback on the official proposal. After a healthy round of discussion I’ll open a PR to submit it for review.
>
>
> # Feature name
>
> * Proposal: [SE-NNNN](NNNN-open-public-protocols.md)
> * Authors: [Matthew Johnson](https://github.com/anandabits)
> * Review Manager: TBD
> * Status: **Awaiting review**
>
> ## Introduction
>
> This proposal introduces `open protocol` and changes the meaning of `public protocol` to match the meaning of `public class` (in this case, conformances are only allowed inside the declaring module).
>
> The pitch thread leading up to this proposal was: [consistent public access modifiers](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031653.html)
>
> ## Motivation
>
> A general principle the Swift community has adopted for access control is that defaults should reserve maximum flexibility for a library. The ensures that any capabilities beyond mere visibility are not available unless the author of the library has explicitly declared their intent that the capabilities be made available. Finally, when it is possible to switch from one semantic to another without breaking clients (but not vice-versa) we should prefer the more forgiving (i.e. fixable) semantic as the (soft) default.
>
> `public` is considered a "soft default" in the sense that it is the first access modifier a user will reach for when exposing a declaration outside of the module. In the case of protocols the current meaning of `public` does not meet the principle of preserving maximum flexibility for the author of the library. It allows users of the library to conform to the protocol.
>
> There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.
>
> Even if all the conforming types are also public there are cases where polymorphism is the preferred implementation. For example, if the set of conforming types is not expected to be fixed (despite all being inside the library) the authors may not want to have to maintain switch statements every time they need to add or remove a confroming type which would be necessary if an enum were used instead. Polymorphism allows us to avoid this, giving us the ability to add and remove conforming types within the implementation of the library without the burden of maintaining switch statements.
>
> Aligning the access modifiers for protocols and classes allows us to specify both conformable and non-conformable protocols, provides a soft default that is consistent with the principle of (soft) defaults reserving maximum flexibility for the library, and increases the overall consistency of the language by aligning the semantics of access control for protocols and classes.
>
> The standard library currently has at least one protocol (`MirrorPath`) that is documented as disallowing client conformances. If this proposal is adopted it is likely that `MirrorPath` would be declared `public protocol` and not `open protocol`.
>
> Jordan Rose has indicated that the Apple frameworks also include a number of protocols documented with the intent that users do not add conformances. Perhaps an importer annotation would allow the compiler to enforce these semantics in Swift code as well.
>
> ## Proposed solution
>
> The proposed solution is to change the meaning of `public protocol` to disallow conformances outside the declaring module and introduce `open protocol` to allow conformances outside the decalring module (equivalent to the current meaning of `public protocol`).
>
> ## Detailed design
>
> The detailed design is relatively straightforward but there are three important wrinkles to consider.
>
> ### User refinement of public protocols
>
> Consider the following example:
>
> ```swift
> // Library module:
> public protocol P {}
> public class C: P {}
>
> // User module:
> protocol User: P {}
> extension C: User {}
> ```
>
> The user module is allowed to add a refinement to `P` because this does not have any impact on the impelementation of the library or its possible evolution. It simply allows the user to write code that is generic over a subset of the conforming types provided by the library.
>
> ### Public protocols with open conforming classes
>
> Consider the following example:
>
> ```swift
> public protocol P P{}
> open class C: P {}
> ```
>
> Users of this module will be able to add subclasses of `C` that have a conformance to `P`. This is allowed becuase the client of the module did not need to explicitly declare a conformance and the module has explicitly stated its intent to allow subclasses of `C` with the `open` access modifier.
>
> ### Open protocols that refine public protocols
>
> Consider the following example:
>
> ```swift
> // library module:
> public protocol P {}
> open protocol Q: P {}
> open protocol R: P {}
>
> // user module:
> struct S: P {} // error `P` is not `open`
> struct T: Q {} // ok
> struct U: R {} // ok
> ```
>
> The user module is allowed to introudce a conformance to `P`, but only indirectly by also conforming to `Q`. The meaning we have ascribed to the keywords implies that this should be allowed and it offers libraries a very wide design space from which to choose. The library is able to have types that conform directly to `P`, while placing additional requirements on user types if necessary.
>
> ## Source compatibility
>
> This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.
>
> 1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
> 2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
> 3. In the subsequent release `public protocol` without annotation becomes an error.
> 4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
> 5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.
>
> ## Effect on ABI stability
>
> I would appreciate it if others can offer input regarding this section. I believe this proposal has ABI consequences, but it's possible that it could be an additivie ABI change where the ABI for conformable protocols remains the same and we add ABI for non-conformable protocols later. If that is possible, the primary impact would be the ABI of any standard library protocols that would prefer to be non-conformable.
>
> ## Effect on API resilience
>
> This proposal would may impact one or more protocols in the standard library, such as `MirrorPath`, which would likely choose to remain `public` rather than adopt `open`.
>
> ## Alternatives considered
>
> The primary alternatives are to either make no change, or to add something like `closed protocol`. The issues motivating the current proposal as a better alternative than either of these options are covered in the motivation section.
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

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


(David Hart) #17

I honestly don't see the use case. The example you showed is an example of how your circumvented the lack of "closed" protocols in Swift. But I'd really like to see an example of the necessity for closed protocols in the first place:

In your case, I would simply defined the API as:

protocol KeyConvertible {
    var key: String { get }
}

public subscript(firstKey: String, parameters: KeyConvertible...) {}

Which would allow clients to implement their own KeyConvertible conforming types which convert into key paths.

···

On 19 Feb 2017, at 09:14, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

If you haven’t followed the other thread Matthew previously opened than you have missed the example I showed there.

Here it is again:

public protocol SubscriptParameterType {
      
    // This property was needed to prevent the client from breaking
    // the library by conforming to the protocol, but I'd like to
    // keep it invisible for the client, or even better prevent the
    // client from conforming to the protocol.
    var parameter: Document.SubscriptParameter { get }
}

extension Document {
      
    public enum SubscriptParameter {
              
        case string(String)
        case integer(Int)
    }
}

extension String : SubscriptParameterType {
      
    public var parameter: Document.SubscriptParameter {
          
        return .string(self)
    }
}

extension Int : SubscriptParameterType {
      
    public var parameter: Document.SubscriptParameter {
          
        return .integer(self)
    }
}

// Somewhere inside the `Document` type
public subscript(firstKey: String, parameters: SubscriptParameterType...) -> Value? { … }
The absence of closed protocols forced me to create a special requirement on that protocol to prevent the client from conforming to that protocol and passing instances of other types my API wouldn’t want to deal with. That creates unnecessary copies and I need to unpack the enum payload to find out which type the user passed. Instead I could simply close the protocol, wouldn’t need the requirement to exist and I could simply cast the type to String or Int when needed.

That implementation enables more safe queries of my Document type like

document["key1", intIndexInstance, stringKeyInstance, 10, "key"]

rather than

document["key1/\(intIndexInstance)/\(stringKeyInstance)/10/key"].

Here is a list of hidden and semi-hidden protocols from the standard library that could be closed. Formatted version: https://gist.github.com/DevAndArtist/168c800d784829be536c407311953ab7

Path Protocol
/swift/stdlib/public/core/AnyHashable.swift:16 _HasCustomAnyHashableRepresentation
/swift/stdlib/public/core/BidirectionalCollection.swift:21 _BidirectionalIndexable
/swift/stdlib/public/core/BridgeObjectiveC.swift:19 _ObjectiveCBridgeable
/swift/stdlib/public/core/Collection.swift:20 _IndexableBase
/swift/stdlib/public/core/Collection.swift:176 _Indexable
/swift/stdlib/public/core/CompilerProtocols.swift:193 _ExpressibleByBuiltinIntegerLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:240 _ExpressibleByBuiltinFloatLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:283 _ExpressibleByBuiltinBooleanLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:316 _ExpressibleByBuiltinUnicodeScalarLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:350 _ExpressibleByBuiltinExtendedGraphemeClusterLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:398 _ExpressibleByBuiltinStringLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:407 _ExpressibleByBuiltinUTF16StringLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:670 _ExpressibleByStringInterpolation
/swift/stdlib/public/core/CompilerProtocols.swift:709 _ExpressibleByColorLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:720 _ExpressibleByImageLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:730 _ExpressibleByFileReferenceLiteral
/swift/stdlib/public/core/CompilerProtocols.swift:750 _DestructorSafeContainer
/swift/stdlib/public/core/FixedPoint.swift.gyb:53 _Integer
/swift/stdlib/public/core/FixedPoint.swift.gyb:70 _SignedInteger
/swift/stdlib/public/core/FixedPoint.swift.gyb:108 _DisallowMixedSignArithmetic
/swift/stdlib/public/core/Hashable.swift:16 _Hashable
/swift/stdlib/public/core/Index.swift:16 _Incrementable
/swift/stdlib/public/core/IntegerArithmetic.swift.gyb:33 _IntegerArithmetic
/swift/stdlib/public/core/Mirror.swift:721 _DefaultCustomPlaygroundQuickLookable
/swift/stdlib/public/core/MutableCollection.swift:20 _MutableIndexable
/swift/stdlib/public/core/NewtypeWrapper.swift.gyb:16 _SwiftNewtypeWrapper
/swift/stdlib/public/core/Pointer.swift:16 _Pointer
/swift/stdlib/public/core/RandomAccessCollection.swift:20 _RandomAccessIndexable
/swift/stdlib/public/core/RangeReplaceableCollection.swift.gyb:27 _RangeReplaceableIndexable
/swift/stdlib/public/core/ReflectionLegacy.swift:41 _Mirror
/swift/stdlib/public/core/ShadowProtocols.swift:27 _ShadowProtocol
/swift/stdlib/public/core/ShadowProtocols.swift:31 _NSFastEnumeration
/swift/stdlib/public/core/ShadowProtocols.swift:41 _NSEnumerator
/swift/stdlib/public/core/ShadowProtocols.swift:51 _NSCopying
/swift/stdlib/public/core/ShadowProtocols.swift:61 _NSArrayCore
/swift/stdlib/public/core/ShadowProtocols.swift:83 _NSDictionaryCore
/swift/stdlib/public/core/ShadowProtocols.swift:125 _NSDictionary
/swift/stdlib/public/core/ShadowProtocols.swift:137 _NSSetCore
/swift/stdlib/public/core/ShadowProtocols.swift:171 _NSSet
/swift/stdlib/public/core/ShadowProtocols.swift:177 _NSNumber
/swift/stdlib/public/core/ShadowProtocols.swift:187 _NSArrayCore
/swift/stdlib/public/core/ShadowProtocols.swift:188 _NSDictionaryCore
/swift/stdlib/public/core/ShadowProtocols.swift:189 _NSSetCore
/swift/stdlib/public/core/StringBridge.swift:194 _NSStringCore
/swift/stdlib/public/SDK/Foundation/NSError.swift:353 _ObjectiveCBridgeableError
/swift/stdlib/public/SDK/Foundation/NSError.swift:379 __BridgedNSError
/swift/stdlib/public/SDK/Foundation/NSError.swift:446 _BridgedNSError
/swift/stdlib/public/SDK/Foundation/NSError.swift:456 _BridgedStoredNSError
/swift/stdlib/public/SDK/Foundation/NSError.swift:564 _ErrorCodeProtocol

--
Adrian Zubarev
Sent with Airmail

Am 19. Februar 2017 um 07:59:45, David Waite via swift-evolution (swift-evolution@swift.org) schrieb:

I am unsure if this feature is a good idea. Does someone have a real-world use for this which isn’t just hiding strong implementation coupling behind a protocol?

When I consume a protocol, it is under the assumption that the protocol is documented such that I would be able to work against *any* implementation of the protocol. With a closed protocol, I would have to assume that there are significant side effects, either undocumented or difficult for a third party to duplicate. To my experience, that sounds brittle.

Assuming you aren’t switching on the implementing type of a protocol (which itself can be a sign that your design isn’t properly using polymorphism), one could get this design by creating a struct with the interface desired, and passing invocations through to an internal protocol reference.

-DW

> On Feb 18, 2017, at 1:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:
>
> Now that we’re in phase 2 I’d like to officially propose we introduce `open` protocols and require conformances to `public` protocols be inside the declaring module. Let’s use this thread for feedback on the official proposal. After a healthy round of discussion I’ll open a PR to submit it for review.
>
>
> # Feature name
>
> * Proposal: [SE-NNNN](NNNN-open-public-protocols.md)
> * Authors: [Matthew Johnson](https://github.com/anandabits)
> * Review Manager: TBD
> * Status: **Awaiting review**
>
> ## Introduction
>
> This proposal introduces `open protocol` and changes the meaning of `public protocol` to match the meaning of `public class` (in this case, conformances are only allowed inside the declaring module).
>
> The pitch thread leading up to this proposal was: [consistent public access modifiers](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031653.html)
>
> ## Motivation
>
> A general principle the Swift community has adopted for access control is that defaults should reserve maximum flexibility for a library. The ensures that any capabilities beyond mere visibility are not available unless the author of the library has explicitly declared their intent that the capabilities be made available. Finally, when it is possible to switch from one semantic to another without breaking clients (but not vice-versa) we should prefer the more forgiving (i.e. fixable) semantic as the (soft) default.
>
> `public` is considered a "soft default" in the sense that it is the first access modifier a user will reach for when exposing a declaration outside of the module. In the case of protocols the current meaning of `public` does not meet the principle of preserving maximum flexibility for the author of the library. It allows users of the library to conform to the protocol.
>
> There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.
>
> Even if all the conforming types are also public there are cases where polymorphism is the preferred implementation. For example, if the set of conforming types is not expected to be fixed (despite all being inside the library) the authors may not want to have to maintain switch statements every time they need to add or remove a confroming type which would be necessary if an enum were used instead. Polymorphism allows us to avoid this, giving us the ability to add and remove conforming types within the implementation of the library without the burden of maintaining switch statements.
>
> Aligning the access modifiers for protocols and classes allows us to specify both conformable and non-conformable protocols, provides a soft default that is consistent with the principle of (soft) defaults reserving maximum flexibility for the library, and increases the overall consistency of the language by aligning the semantics of access control for protocols and classes.
>
> The standard library currently has at least one protocol (`MirrorPath`) that is documented as disallowing client conformances. If this proposal is adopted it is likely that `MirrorPath` would be declared `public protocol` and not `open protocol`.
>
> Jordan Rose has indicated that the Apple frameworks also include a number of protocols documented with the intent that users do not add conformances. Perhaps an importer annotation would allow the compiler to enforce these semantics in Swift code as well.
>
> ## Proposed solution
>
> The proposed solution is to change the meaning of `public protocol` to disallow conformances outside the declaring module and introduce `open protocol` to allow conformances outside the decalring module (equivalent to the current meaning of `public protocol`).
>
> ## Detailed design
>
> The detailed design is relatively straightforward but there are three important wrinkles to consider.
>
> ### User refinement of public protocols
>
> Consider the following example:
>
> ```swift
> // Library module:
> public protocol P {}
> public class C: P {}
>
> // User module:
> protocol User: P {}
> extension C: User {}
> ```
>
> The user module is allowed to add a refinement to `P` because this does not have any impact on the impelementation of the library or its possible evolution. It simply allows the user to write code that is generic over a subset of the conforming types provided by the library.
>
> ### Public protocols with open conforming classes
>
> Consider the following example:
>
> ```swift
> public protocol P P{}
> open class C: P {}
> ```
>
> Users of this module will be able to add subclasses of `C` that have a conformance to `P`. This is allowed becuase the client of the module did not need to explicitly declare a conformance and the module has explicitly stated its intent to allow subclasses of `C` with the `open` access modifier.
>
> ### Open protocols that refine public protocols
>
> Consider the following example:
>
> ```swift
> // library module:
> public protocol P {}
> open protocol Q: P {}
> open protocol R: P {}
>
> // user module:
> struct S: P {} // error `P` is not `open`
> struct T: Q {} // ok
> struct U: R {} // ok
> ```
>
> The user module is allowed to introudce a conformance to `P`, but only indirectly by also conforming to `Q`. The meaning we have ascribed to the keywords implies that this should be allowed and it offers libraries a very wide design space from which to choose. The library is able to have types that conform directly to `P`, while placing additional requirements on user types if necessary.
>
> ## Source compatibility
>
> This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.
>
> 1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
> 2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
> 3. In the subsequent release `public protocol` without annotation becomes an error.
> 4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
> 5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.
>
> ## Effect on ABI stability
>
> I would appreciate it if others can offer input regarding this section. I believe this proposal has ABI consequences, but it's possible that it could be an additivie ABI change where the ABI for conformable protocols remains the same and we add ABI for non-conformable protocols later. If that is possible, the primary impact would be the ABI of any standard library protocols that would prefer to be non-conformable.
>
> ## Effect on API resilience
>
> This proposal would may impact one or more protocols in the standard library, such as `MirrorPath`, which would likely choose to remain `public` rather than adopt `open`.
>
> ## Alternatives considered
>
> The primary alternatives are to either make no change, or to add something like `closed protocol`. The issues motivating the current proposal as a better alternative than either of these options are covered in the motivation section.
>
> _______________________________________________
> 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

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


(David Hart) #18

I still don't see the use case for this. Perhaps I'm wrong, but if an API creates a protocol for the sole purpose of representing a set of concrete types, that looks more to me like bad API design, and not a missing feature in the language. Can you give me a small concrete real-world example of an API which requires that?

···

On 19 Feb 2017, at 16:10, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Sent from my iPad

On Feb 19, 2017, at 12:58 AM, David Waite <david@alkaline-solutions.com> wrote:

I am unsure if this feature is a good idea. Does someone have a real-world use for this which isn’t just hiding strong implementation coupling behind a protocol?

Yes. If this proposal moves ahead we can implement an API that takes a hereogenous collection with implicit conversion for several different concrete types while ensuring that all elements are of a type provided by the library:

public protocol Foo {}
public struct One: Foo {}
public struct Two: Foo {}
// other conforming types in the library

func takesFoos(_ foos: [Foo]) {}

// in user code:
takesFoos([One(), One(), Two(), One()])

This use case could be subsumed by enums if we implement some of the ideas in my value subtyping manifesto but those are for the future and may or may not be accepted. This is a relatively small change that can e made in Swift 4.

When I consume a protocol, it is under the assumption that the protocol is documented such that I would be able to work against *any* implementation of the protocol. With a closed protocol, I would have to assume that there are significant side effects, either undocumented or difficult for a third party to duplicate. To my experience, that sounds brittle.

Assuming you aren’t switching on the implementing type of a protocol (which itself can be a sign that your design isn’t properly using polymorphism), one could get this design by creating a struct with the interface desired, and passing invocations through to an internal protocol reference.

-DW

On Feb 18, 2017, at 1:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Now that we’re in phase 2 I’d like to officially propose we introduce `open` protocols and require conformances to `public` protocols be inside the declaring module. Let’s use this thread for feedback on the official proposal. After a healthy round of discussion I’ll open a PR to submit it for review.

# Feature name

* Proposal: [SE-NNNN](NNNN-open-public-protocols.md)
* Authors: [Matthew Johnson](https://github.com/anandabits)
* Review Manager: TBD
* Status: **Awaiting review**

## Introduction

This proposal introduces `open protocol` and changes the meaning of `public protocol` to match the meaning of `public class` (in this case, conformances are only allowed inside the declaring module).

The pitch thread leading up to this proposal was: [consistent public access modifiers](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031653.html)

## Motivation

A general principle the Swift community has adopted for access control is that defaults should reserve maximum flexibility for a library. The ensures that any capabilities beyond mere visibility are not available unless the author of the library has explicitly declared their intent that the capabilities be made available. Finally, when it is possible to switch from one semantic to another without breaking clients (but not vice-versa) we should prefer the more forgiving (i.e. fixable) semantic as the (soft) default.

`public` is considered a "soft default" in the sense that it is the first access modifier a user will reach for when exposing a declaration outside of the module. In the case of protocols the current meaning of `public` does not meet the principle of preserving maximum flexibility for the author of the library. It allows users of the library to conform to the protocol.

There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.

Even if all the conforming types are also public there are cases where polymorphism is the preferred implementation. For example, if the set of conforming types is not expected to be fixed (despite all being inside the library) the authors may not want to have to maintain switch statements every time they need to add or remove a confroming type which would be necessary if an enum were used instead. Polymorphism allows us to avoid this, giving us the ability to add and remove conforming types within the implementation of the library without the burden of maintaining switch statements.

Aligning the access modifiers for protocols and classes allows us to specify both conformable and non-conformable protocols, provides a soft default that is consistent with the principle of (soft) defaults reserving maximum flexibility for the library, and increases the overall consistency of the language by aligning the semantics of access control for protocols and classes.

The standard library currently has at least one protocol (`MirrorPath`) that is documented as disallowing client conformances. If this proposal is adopted it is likely that `MirrorPath` would be declared `public protocol` and not `open protocol`.

Jordan Rose has indicated that the Apple frameworks also include a number of protocols documented with the intent that users do not add conformances. Perhaps an importer annotation would allow the compiler to enforce these semantics in Swift code as well.

## Proposed solution

The proposed solution is to change the meaning of `public protocol` to disallow conformances outside the declaring module and introduce `open protocol` to allow conformances outside the decalring module (equivalent to the current meaning of `public protocol`).

## Detailed design

The detailed design is relatively straightforward but there are three important wrinkles to consider.

### User refinement of public protocols

Consider the following example:

// Library module:
public protocol P {}
public class C: P {}

// User module:
protocol User: P {}
extension C: User {}

The user module is allowed to add a refinement to `P` because this does not have any impact on the impelementation of the library or its possible evolution. It simply allows the user to write code that is generic over a subset of the conforming types provided by the library.

### Public protocols with open conforming classes

Consider the following example:

public protocol P P{}
open class C: P {}

Users of this module will be able to add subclasses of `C` that have a conformance to `P`. This is allowed becuase the client of the module did not need to explicitly declare a conformance and the module has explicitly stated its intent to allow subclasses of `C` with the `open` access modifier.

### Open protocols that refine public protocols

Consider the following example:

// library module:
public protocol P {}
open protocol Q: P {}
open protocol R: P {}

// user module:
struct S: P {} // error `P` is not `open`
struct T: Q {} // ok
struct U: R {} // ok

The user module is allowed to introudce a conformance to `P`, but only indirectly by also conforming to `Q`. The meaning we have ascribed to the keywords implies that this should be allowed and it offers libraries a very wide design space from which to choose. The library is able to have types that conform directly to `P`, while placing additional requirements on user types if necessary.

## Source compatibility

This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.

1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
3. In the subsequent release `public protocol` without annotation becomes an error.
4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.

## Effect on ABI stability

I would appreciate it if others can offer input regarding this section. I believe this proposal has ABI consequences, but it's possible that it could be an additivie ABI change where the ABI for conformable protocols remains the same and we add ABI for non-conformable protocols later. If that is possible, the primary impact would be the ABI of any standard library protocols that would prefer to be non-conformable.

## Effect on API resilience

This proposal would may impact one or more protocols in the standard library, such as `MirrorPath`, which would likely choose to remain `public` rather than adopt `open`.

## Alternatives considered

The primary alternatives are to either make no change, or to add something like `closed protocol`. The issues motivating the current proposal as a better alternative than either of these options are covered in the motivation section.

_______________________________________________
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


(Xiaodi Wu) #19

>
> I am unsure if this feature is a good idea. Does someone have a
real-world use for this which isn’t just hiding strong implementation
coupling behind a protocol?

Strong coupling is sometimes inevitable.

In a previous thread, I brought up an example of a place I would use this
feature: Wrapping the SQLite APIs. For instance:

        public protocol SQLiteValue {
                init(statement: SQLiteStatement, columnAt index: Int)
throws
                func bind(to statement: SQLiteStatement, at index: Int)
throws
        }
        extension Int: SQLiteValue {
                public init(statement: SQLiteStatement, columnAt index:
Int) throws {
                        self = sqlite3_column_int(statement.stmt, index)
                }
                public func bind(to statement: SQLiteStatement, at index:
Int) throws {
                        try throwIfNotOK(
                                sqlite3_bind_int64(statement.stmt, index,
self)
                        )
                }
        }
        extension Double: SQLiteValue {…}
        extension Data: SQLiteValue {…}
        extension String: SQLiteValue {…}
        extension Optional: SQLiteValue where Wrapped: SQLiteValue {…}

This is a case of your hated "strong implementation coupling". But the
coupling is to a library that ultimately writes data to disk in a portable
format. Strong coupling here is inevitable.

What is the purpose of permitting outside conformances to `SQLiteValue`?

Suppose you released this library and I used it for my SQLite needs. But my
application uses Float behind the scenes, which can be losslessly promoted
(as you must know) as Double. For convenience of interfacing with your
library, I conform Float to SQLiteValue. Likewise, let's say I implement a
new Rational type (not actually hypothetical, hehe), and I decide that I
want to store that as Double (it'd be precise enough once all the
calculations are done). For convenience, I conform Rational<Int> to
SQLiteValue. I could make good on these conformances by having `init()` and
`bind(to:at:)` do a numeric cast and then forward to the relevant methods
on Double. Now, I understand why your library wouldn't do this, since
willy-nilly conversions between types are worth at least careful thought,
but as a non-library client that uses Float pervasively, I'd have good
justification for that extension.

There is no useful way to conform to `SQLiteValue`; the underlying library

supports certain types, and I've implemented support for those types.
Allowing outside conformances can only mislead people into fruitlessly
trying to conform their types, not realizing that the calls they need
simply aren't exposed.

What is the harm of permitting an outside conformance to `SQLiteValue`?

Moreover, exposing these details unnecessarily freezes the design of
`SQLiteValue`. If I want to change the design of this parameter handling in
a future version, well, too bad, the API is public, I'm stuck. *For an API
I don't intend anyone to conform to publicly in the first place.* That kind
of sucks, doesn't it?

But with `public` vs `open`, your API is still frozen whichever you choose.
Even if you can't conform to `SQLiteValue`, there is no guarantee that your
clients won't _invoke_ `SQLiteValue.bind(to:at:)`, so you can't redesign
the protocol anyway. What other flexibility are you looking for?

···

On Sun, Feb 19, 2017 at 5:15 PM, Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

> On Feb 18, 2017, at 10:58 PM, David Waite via swift-evolution < > swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

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


(David Waite) #20

<snip>

Here is a list of hidden and semi-hidden protocols from the standard library that could be closed. Formatted version: https://gist.github.com/DevAndArtist/168c800d784829be536c407311953ab7The majority of this list seems to more likely be non-resilient, appropriate to be hidden rather than being closed. Several of these I believe only exist due to features which are not yet in the Swift language, such as conditional conformances and generalized existentials.

The types in the standard library which I think are relevant to a closed-protocol/union-type discussion would be:

CVarArg - interface of types appropriate to withVaList. “Closed” nature already enforced by compiler, e.g.;
class FooBar : CVarArg {} //error: type 'FooBar' does not conform to protocol ‘CVarArg’

AFAICT, there is no reason that a third party type *couldn’t* implement CVarArg, other than the requirements of the protocol being considered private to the standard library (and possibly non-resilient). A valid protocol implementation returns the data (as an [Int] array) that is to be appended to the va_list.

MirrorPath - conceptually very similar to the SubscriptParameter, supporting String, Int and IntMax types. While the protocol is not closed, Mirror.descendent will preconditionFail if a different type other than String, Int, or IntMax is passed in.

-DW

···

On Feb 19, 2017, at 1:14 AM, Adrian Zubarev <adrian.zubarev@devandartist.com> wrote:

--
Adrian Zubarev
Sent with Airmail

Am 19. Februar 2017 um 07:59:45, David Waite via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

I am unsure if this feature is a good idea. Does someone have a real-world use for this which isn’t just hiding strong implementation coupling behind a protocol?

When I consume a protocol, it is under the assumption that the protocol is documented such that I would be able to work against *any* implementation of the protocol. With a closed protocol, I would have to assume that there are significant side effects, either undocumented or difficult for a third party to duplicate. To my experience, that sounds brittle.

Assuming you aren’t switching on the implementing type of a protocol (which itself can be a sign that your design isn’t properly using polymorphism), one could get this design by creating a struct with the interface desired, and passing invocations through to an internal protocol reference.

-DW

> On Feb 18, 2017, at 1:41 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:
>
> Now that we’re in phase 2 I’d like to officially propose we introduce `open` protocols and require conformances to `public` protocols be inside the declaring module. Let’s use this thread for feedback on the official proposal. After a healthy round of discussion I’ll open a PR to submit it for review.
>
>
> # Feature name
>
> * Proposal: [SE-NNNN](NNNN-open-public-protocols.md)
> * Authors: [Matthew Johnson](https://github.com/anandabits)
> * Review Manager: TBD
> * Status: **Awaiting review**
>
> ## Introduction
>
> This proposal introduces `open protocol` and changes the meaning of `public protocol` to match the meaning of `public class` (in this case, conformances are only allowed inside the declaring module).
>
> The pitch thread leading up to this proposal was: [consistent public access modifiers](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031653.html)
>
> ## Motivation
>
> A general principle the Swift community has adopted for access control is that defaults should reserve maximum flexibility for a library. The ensures that any capabilities beyond mere visibility are not available unless the author of the library has explicitly declared their intent that the capabilities be made available. Finally, when it is possible to switch from one semantic to another without breaking clients (but not vice-versa) we should prefer the more forgiving (i.e. fixable) semantic as the (soft) default.
>
> `public` is considered a "soft default" in the sense that it is the first access modifier a user will reach for when exposing a declaration outside of the module. In the case of protocols the current meaning of `public` does not meet the principle of preserving maximum flexibility for the author of the library. It allows users of the library to conform to the protocol.
>
> There are good reasons a library may not wish to allow users to add conformances to a protocol. For example, it may not wish to expose the conforming concrete types. While similar behavior could be accomplished with an enum if cases could be private, that requires an implementation to use switch statements rather than polymorphism.
>
> Even if all the conforming types are also public there are cases where polymorphism is the preferred implementation. For example, if the set of conforming types is not expected to be fixed (despite all being inside the library) the authors may not want to have to maintain switch statements every time they need to add or remove a confroming type which would be necessary if an enum were used instead. Polymorphism allows us to avoid this, giving us the ability to add and remove conforming types within the implementation of the library without the burden of maintaining switch statements.
>
> Aligning the access modifiers for protocols and classes allows us to specify both conformable and non-conformable protocols, provides a soft default that is consistent with the principle of (soft) defaults reserving maximum flexibility for the library, and increases the overall consistency of the language by aligning the semantics of access control for protocols and classes.
>
> The standard library currently has at least one protocol (`MirrorPath`) that is documented as disallowing client conformances. If this proposal is adopted it is likely that `MirrorPath` would be declared `public protocol` and not `open protocol`.
>
> Jordan Rose has indicated that the Apple frameworks also include a number of protocols documented with the intent that users do not add conformances. Perhaps an importer annotation would allow the compiler to enforce these semantics in Swift code as well.
>
> ## Proposed solution
>
> The proposed solution is to change the meaning of `public protocol` to disallow conformances outside the declaring module and introduce `open protocol` to allow conformances outside the decalring module (equivalent to the current meaning of `public protocol`).
>
> ## Detailed design
>
> The detailed design is relatively straightforward but there are three important wrinkles to consider.
>
> ### User refinement of public protocols
>
> Consider the following example:
>
> ```swift
> // Library module:
> public protocol P {}
> public class C: P {}
>
> // User module:
> protocol User: P {}
> extension C: User {}
> ```
>
> The user module is allowed to add a refinement to `P` because this does not have any impact on the impelementation of the library or its possible evolution. It simply allows the user to write code that is generic over a subset of the conforming types provided by the library.
>
> ### Public protocols with open conforming classes
>
> Consider the following example:
>
> ```swift
> public protocol P P{}
> open class C: P {}
> ```
>
> Users of this module will be able to add subclasses of `C` that have a conformance to `P`. This is allowed becuase the client of the module did not need to explicitly declare a conformance and the module has explicitly stated its intent to allow subclasses of `C` with the `open` access modifier.
>
> ### Open protocols that refine public protocols
>
> Consider the following example:
>
> ```swift
> // library module:
> public protocol P {}
> open protocol Q: P {}
> open protocol R: P {}
>
> // user module:
> struct S: P {} // error `P` is not `open`
> struct T: Q {} // ok
> struct U: R {} // ok
> ```
>
> The user module is allowed to introudce a conformance to `P`, but only indirectly by also conforming to `Q`. The meaning we have ascribed to the keywords implies that this should be allowed and it offers libraries a very wide design space from which to choose. The library is able to have types that conform directly to `P`, while placing additional requirements on user types if necessary.
>
> ## Source compatibility
>
> This proposal breaks source compatibility, but in a way that allows for a simple mechanical migration. A multi-release stratgegy will be used to roll out this proposal to provide maximum possible source compatibility from one release to the next.
>
> 1. In Swift 4, introduce the `open` keyword and the `@nonopen` attribute (which can be applied to `public protocol` to give it the new semantics of `public`).
> 2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol` with no annotation.
> 3. In the subsequent release `public protocol` without annotation becomes an error.
> 4. In the subsequent relase `public protocol` without annotation takes on the new semantics.
> 5. `@nonopen` becomes a warning, and evenutally an erro as soon as we are comfortable making those changes.
>
> ## Effect on ABI stability
>
> I would appreciate it if others can offer input regarding this section. I believe this proposal has ABI consequences, but it's possible that it could be an additivie ABI change where the ABI for conformable protocols remains the same and we add ABI for non-conformable protocols later. If that is possible, the primary impact would be the ABI of any standard library protocols that would prefer to be non-conformable.
>
> ## Effect on API resilience
>
> This proposal would may impact one or more protocols in the standard library, such as `MirrorPath`, which would likely choose to remain `public` rather than adopt `open`.
>
> ## Alternatives considered
>
> The primary alternatives are to either make no change, or to add something like `closed protocol`. The issues motivating the current proposal as a better alternative than either of these options are covered in the motivation section.
>
> _______________________________________________
> 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