> On Feb 10, 2017, at 12:52 PM, Jordan Rose <jordan_r...@apple.com> wrote:
> 
> Hi, Matthew. Thank you for bringing up these issues. I'm going to break my 
> feedback up into separate messages, because I think really the enum and 
> protocol cases are unrelated. Open classes refer to classes that can be 
> subclassed from clients of the current module, and similarly open protocols 
> would be protocols that can be adopted from clients of the current module. 
> Public-but-not-open classes cannot be subclassed from outside the current 
> module, but they can still be subclassed within the module. By contrast, 
> "open" enums can grow new cases in new versions of the library, but clients 
> still can't add cases. (That's not a totally unreasonable feature to ever 
> consider, but it's not the one we need now.)

Hi Jordan.  Thanks for replying to my post!

I understand the current behavior of `open` and how it would work for 
protocols.  I also understand the vocabulary that has been used in talking 
about “open” enums thus far.  What I am trying to point out is that there are 
inconsistencies in the vocabulary we’ve been using thus far.  

The ideas of “open” and “closed” both talk about who is able to add to the set 
of cases / subclasses / conforming types.  `public` (without annotation) also 
does this.  But we haven’t been using the terms consistently - we use a 
different meaning depending on which kind of entity we’re talking about.

For example, as you pointed out, `open` currently means both a module *and* its 
clients can add new subclasses.  It doesn’t seem right to use this same 
terminology to mean the module can add cases to an enum but clients *can’t* add 
new cases to the enum.  I understand that “open” enums in the sense of the 
current meaning of the `open` keyword are not a feature we need right away.  I 
noted that this is specifically not proposed in my pitch.  But if we ever *do* 
add this feature, `open enum` seems like the right way to spell it (probably 
just using the existing case syntax in an extension to add cases in the client).

It’s also worth pointing out that `public` currently has three distinct 
meanings:
* `public` enums *cannot* have a new case added in a future version of the 
library is without a breaking change
* `public` classes *can* have a new subclass added in a future version of the 
library without a breaking change, but clients cannot add subclasses
* `public` protocols have the same semantics as `open` classes, allowing 
clients to add conforming types

If we want to move forward with *no* breaking change of any kind the situation 
gets worse:
* `open` enums can have new cases added in a future version of the library, but 
clients cannot add cases
* `closed` protocols behave like `public` classes while using the same 
terminology we use for enums with a fixed set of public cases (to which future 
versions of the library may not add to)

This all seems pretty unfortunate to me.  It seems somewhat confusing in a 
totally unnecessary way.  Everyone could learn to live with it, but why should 
we?  I think there is a very reasonable way to use this terminology 
consistently across all kinds of types.


> 
> This message will talk about protocols (and a bit about classes); I'll put my 
> thoughts on enums in another message.
> 
> I'm with you (and Adrian) in thinking "public-but-not-open" protocols are 
> useful. The Apple frameworks have a good handful of these, for cases where a 
> framework doesn't want to tie itself to a particular type, but still wants to 
> vend something with certain operations. Public-but-not-open protocols also 
> allow for non-public requirements, i.e. operations that every conforming type 
> has but only the library needs to call.

I’m glad to hear this!

> 
> However, we still have a hurdle here: is this useful enough to change the 
> defaults for it? Another way to implement this is to leave 'public' as is 
> (with its open-like behavior), but have an annotation to say that it can only 
> be conformed to from within the module. (`@closed public protocol Foo`) This 
> isn't necessarily what we would have done from the start, but breaks between 
> Swift 3 and Swift 4 have a higher bar to clear than between Swift 2 and Swift 
> 3.

This is certainly an option.  I feel like there is a pretty strong case for not 
going this route though: 

* Consistent use of terminology is pretty important.
* The decision to not pick a default between `public` and `open` classes was a 
good one which applies to protocols and enums as well.
* While it is a breaking change, the impact can be minimal with a multi-release 
rollout.

Many Swift users will run a migrator when upgrading to a new version of Swift.  
For these users, a simple mechanical migration will keep their code running 
without warnings through each version transition.  Users who don’t use the 
migrator will receive a warning in one version which becomes an error in the 
next, giving them plenty of time to update their code without preventing it 
from compiling and with no semantic impact.


> 
> It's worth noting that this can all be emulated by a struct with a non-public 
> field of protocol type and forwarding operations, but that's a lot of extra 
> work today. We want to make the correct thing easy; in the face of writing a 
> wrapper type, I suspect many library authors would just give up and make the 
> protocol public.
> 
> I don't think the proposed "closed" for classes and protocols is interesting. 
> "No new subclasses/adopters" seems only marginally useful for optimization, 
> and not at all useful at a semantic level. "public-but-not-open" is the 
> interesting access level.

I agree that there may not be interesting use cases for this.  I would be happy 
to treat them the same way as `open enum`: as something that could potentially 
be added in the future that we know how to spell if we ever decide to add it.  
The primary point I am making is that there is a semantic equivalent of closed 
enums for classes and protocols.  Perhaps somebody will find an interesting use 
case.  If we adopt consistent terminology we know how to apply it to these use 
cases.

> 
> That's about all I've got for protocols. Thanks again for bringing it up.
> Jordan
> 
> P.S. For classes, note that 'final' is essentially a performance optimization 
> at this point. I'm not even sure we should bother displaying it in generated 
> interfaces (although the compiler should still be able to take advantage of 
> it in clients).

`final` can be pretty useful when reasoning about code.  It’s more than just a 
performance optimization.  It also represents a compiler proof about our code.

> 
> 
> 
>> On Feb 8, 2017, at 15:05, Matthew Johnson via swift-evolution 
>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>> 
>> I’ve been thinking a lot about our public access modifier story lately in 
>> the context of both protocols and enums.  I believe we should move further 
>> in the direction we took when introducing the `open` keyword.  I have 
>> identified what I think is a promising direction and am interested in 
>> feedback from the community.  If community feedback is positive I will flesh 
>> this out into a more complete proposal draft.
>> 
>> 
>> Background and Motivation:
>> 
>> In Swift 3 we had an extended debate regarding whether or not to allow 
>> inheritance of public classes by default or to require an annotation for 
>> classes that could be subclassed outside the module.  The decision we 
>> reached was to avoid having a default at all, and instead make `open` an 
>> access modifier.  The result is library authors are required to consider the 
>> behavior they wish for each class.  Both behaviors are equally convenient 
>> (neither is penalized by requiring an additional boilerplate-y annotation).
>> 
>> A recent thread 
>> (https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031566.html
>>  
>> <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031566.html>)
>>  discussed a similar tradeoff regarding whether public enums should commit 
>> to a fixed set of cases by default or not.  The current behavior is that 
>> they *do* commit to a fixed set of cases and there is no option (afaik) to 
>> modify that behavior.  The Library Evolution document 
>> (https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums 
>> <https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums>)
>>  suggests a desire to change this before locking down ABI such that public 
>> enums *do not* make this commitment by default, and are required to opt-in 
>> to this behavior using an `@closed` annotation.
>> 
>> In the previous discussion I stated a strong preference that closed enums 
>> *not* be penalized with an additional annotation.  This is because I feel 
>> pretty strongly that it is a design smell to: 1) expose cases publicly if 
>> consumers of the API are not expected to switch on them and 2) require users 
>> to handle unknown future cases if they are likely to switch over the cases 
>> in correct use of the API.
>> 
>> The conclusion I came to in that thread is that we should adopt the same 
>> strategy as we did with classes: there should not be a default.
>> 
>> There have also been several discussions both on the list and via Twitter 
>> regarding whether or not we should allow closed protocols.  In a recent 
>> Twitter discussion Joe Groff suggested that we don’t need them because we 
>> should use an enum when there is a fixed set of conforming types.  There are 
>> at least two  reasons why I still think we *should* add support for closed 
>> protocols.
>> 
>> As noted above (and in the previous thread in more detail), if the set of 
>> types (cases) isn’t intended to be fixed (i.e. the library may add new types 
>> in the future) an enum is likely not a good choice.  Using a closed protocol 
>> discourages the user from switching and prevents the user from adding 
>> conformances that are not desired.
>> 
>> Another use case supported by closed protocols is a design where users are 
>> not allowed to conform directly to a protocol, but instead are required to 
>> conform to one of several protocols which refine the closed protocol.  Enums 
>> are not a substitute for this use case.  The only option is to resort to 
>> documentation and runtime checks.
>> 
>> 
>> Proposal:
>> 
>> This proposal introduces the new access modifier `closed` as well as 
>> clarifying the meaning of `public` and expanding the use of `open`.  This 
>> provides consistent capabilities and semantics across enums, classes and 
>> protocols.
>> 
>> `open` is the most permissive modifier.  The symbol is visible outside the 
>> module and both users and future versions of the library are allowed to add 
>> new cases, subclasses or conformances.  (Note: this proposal does not 
>> introduce user-extensible `open` enums, but provides the syntax that would 
>> be used if they are added to the language)
>> 
>> `public` makes the symbol visible without allowing the user to add new 
>> cases, subclasses or conformances.  The library reserves the right to add 
>> new cases, subclasses or conformances in a future version.
>> 
>> `closed` is the most restrictive modifier.  The symbol is visible publicly 
>> with the commitment that future versions of the library are *also* 
>> prohibited from adding new cases, subclasses or conformances.  Additionally, 
>> all cases, subclasses or conformances must be visible outside the module.
>> 
>> Note: the `closed` modifier only applies to *direct* subclasses or 
>> conformances.  A subclass of a `closed` class need not be `closed`, in fact 
>> it may be `open` if the design of the library requires that.  A class that 
>> conforms to a `closed` protocol also need not be `closed`.  It may also be 
>> `open`.  Finally, a protocol that refines a `closed` protocol need not be 
>> `closed`.  It may also be `open`.
>> 
>> This proposal is consistent with the principle that libraries should opt-in 
>> to all public API contracts without taking a position on what that contract 
>> should be.  It does this in a way that offers semantically consistent 
>> choices for API contract across classes, enums and protocols.  The result is 
>> that the language allows us to choose the best tool for the job without 
>> restricting the designs we might consider because some kinds of types are 
>> limited with respect to the `open`, `public` and `closed` semantics a design 
>> might require.
>> 
>> 
>> Source compatibility:
>> 
>> This proposal affects both public enums and public protocols.  The current 
>> behavior of enums is equivalent to a `closed` enum under this proposal and 
>> the current behavior of protocols is equivalent to an `open` protocol under 
>> this proposal.  Both changes allow for a simple mechanical migration, but 
>> that may not be sufficient given the source compatibility promise made for 
>> Swift 4.  We may need to identify a multi-release strategy for adopting this 
>> proposal.
>> 
>> Brent Royal-Gordon suggested such a strategy in a discussion regarding 
>> closed protocols on Twitter:
>> 
>> * In Swift 4: all unannotated public protocols receive a warning, possibly 
>> with a fix-it to change the annotation to `open`.
>> * Also in Swift 4: an annotation is introduced to opt-in to the new `public` 
>> behavior.  Brent suggested `@closed`, but as this proposal distinguishes 
>> `public` and `closed` we would need to identify something else.  I will use 
>> `@annotation` as a placeholder.
>> * Also In Swift 4: the `closed` modifier is introduced.
>> 
>> * In Swift 5 the warning becomes a compiler error.  `public protocol` is not 
>> allowed.  Users must use `@annotation public protocol`.
>> * In Swift 6 `public protocol` is allowed again, now with the new semantics. 
>>  `@annotation public protocol` is also allowed, now with a warning and a 
>> fix-it to remove the warning.
>> * In Swift 7 `@annotation public protocol` is no longer allowed.
>> 
>> A similar mult-release strategy would work for migrating public enums.
>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 

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

Reply via email to