> On Feb 12, 2017, at 10:39 AM, Nevin Brackett-Rozinsky via swift-evolution 
> <[email protected]> wrote:
> 
> Alternative: leave “public enum” as it is now, and spell the resilient 
> version “@resilient enum”

The problem with this approach is that the “default” is the stricter contract 
and library authors have to remember to add the annotation to opt-out of that 
stricter contract.  The problems created by the stricter contract will only 
appear later when the author realizes they need to add new cases and now it’s a 
breaking change.  

Responsible library authors should always make an intentional choice, but 
sometimes even the best of us make mistakes.  If a library author makes this 
mistake it is likely that it won’t be noticed until it is too late.  Requiring 
the library author to make a choice between mutually exclusive options rather 
than a choice to add or omit an annotation reduces the chance of the library 
author making this error.  

This is the rationale that led to us adding `open` rather than adding something 
like an `@closed` annotation for classes.  The desire to avoid growing lots of 
annotations in the language was also an important consideration that I believe 
applies here.

> 
> Nevin
> 
> 
> On Sunday, February 12, 2017, Matthew Johnson via swift-evolution 
> <[email protected] <mailto:[email protected]>> wrote:
> 
>> On Feb 12, 2017, at 10:24 AM, David Hart <[email protected] 
>> <javascript:_e(%7B%7D,'cvml','[email protected]');>> wrote:
>> 
>> 
>> On 12 Feb 2017, at 16:38, Matthew Johnson <[email protected] 
>> <javascript:_e(%7B%7D,'cvml','[email protected]');>> wrote:
>> 
>>> 
>>>> On Feb 12, 2017, at 12:50 AM, David Hart <[email protected] 
>>>> <javascript:_e(%7B%7D,'cvml','[email protected]');>> wrote:
>>>> 
>>>> Hi Matthew,
>>>> 
>>>> I've read your proposal ideas and most of the discussions on the thread, 
>>>> and I'd like to provide some personal feedback.
>>>> 
>>>> Swift already has a complicated "access modifier" story so I think we 
>>>> really want a good reason to introduce a new one. And the problem I see is 
>>>> that `closed` has much less semantic weight than the other modifiers.
>>> 
>>> How so?  I’m not sure if I catch your meaning here.  It feels to me like it 
>>> has the same semantic weight as `open`: prohibiting future versions of a 
>>> module from adding cases / subclasses / conformances is roughly the inverse 
>>> of lifting the restriction that clients cannot add those things.  Therefore 
>>> it has roughly the same degree of additional meaning over `public` as 
>>> `open` does.
>> 
>> The difference I see is precisely that 'public' and 'open' modifiers limit 
>> what the client of a module can do while closed limits what future versions 
>> of a module can do. Feels quite different to me.
> 
> This is a reasonable point and is perhaps the strongest argument made against 
> my proposal thus far.  However, I think we have to consider my proposal 
> relative to the alternatives.  
> 
> The only alternative I am aware of is making `public enum` the resilient 
> variety and using `@closed public enum` for the closed variety.  This means 
> that `public` will have at least two different semantics (three if we don’t 
> reconcile classes and protocols).  It also means that the resilient variety 
> is effectively the default.  I am really happy that we decide not to have a 
> default between `open` and `public` and think the best choice is that we 
> don’t have one here either.  The fact that we have a way to do this while 
> solving the inconsistent semantics of `public` feels like a net win to me.
> 
>> 
>>>> 
>>>> First of all, the Library Evolution document you linked says toward at the 
>>>> top that "this document is primarily concerned with binary compatibility, 
>>>> i.e. what changes can safely be made to a library between releases that 
>>>> will not break memory-safety or type-safety, or cause clients to fail to 
>>>> run at all." It seems to me that the @closed introduced in that document 
>>>> is much more about library resilience than about only closing down the 
>>>> addition of new cases: that's why it also talks about reordering and all 
>>>> other changes that can change the memory layout.
>>>> 
>>>> Swift 3 having introduced both fileprivate and open has complexified the 
>>>> access level story for developers and library authors. That complexity is 
>>>> the cost that we have paid for more expressiveness. But if we continue 
>>>> adding new access control modifiers to express new semantics, we may be 
>>>> going too far: perfect is the enemy of good.
>>>> 
>>>> Both of those arguments explain why I think closed should be introduced, 
>>>> but only as a rarely-used attribute for library authors which need to 
>>>> express ABI resilience, and not as an extra access modifier.
>>> 
>>> `closed` is about much more than binary compatibility.  Any time a library 
>>> publishes an enum that clients can reasonably be expected to switch 
>>> statements over the library should strive to make it `closed` wherever 
>>> possible.  Otherwise clients are expected to handle unknown future cases by 
>>> design.  That is a design smell if you ask me.  This means that we can 
>>> expect libraries to often carefully design such enums in a way that allows 
>>> them to be `closed`.  The use case for resilient enums is in things like 
>>> mutually exclusive option sets received as input to the module and for 
>>> which it would be unusual for clients of the library to write a switch 
>>> statement over.
>>> 
>>> With this in mind, `closed` should not be a rarely-used attribute at all.  
>>> In fact it will often be the best choice.  This is a big motivation behind 
>>> my desire to see it on equal footing with `public` and `open`.
>>> 
>>> In regards to the complexity of the access model - if you look closely, 
>>> `public` has three subtly different meanings today.  That kind of 
>>> inconsistency is part of the complexity of it.  And as noted, `closed` is a 
>>> concept that *will* play a significant role in Swift, regardless of how we 
>>> spell it.  What my proposal aims to do is to incorporate it into a 
>>> consistent system of outside-the-module access modifiers.  
>>> 
>>> One can make a very reasonable argument that access modifiers should *only* 
>>> be in the business of talking about visibility and should stay out of the 
>>> business of talking about “who can add to the set of cases / subclasses / 
>>> conformances”.  The time for that argument was when we had the `open` 
>>> discussion last year.  I happen to like the direction we went because it 
>>> places `public` and `open` on equal footing.  And now that we *have* 
>>> decided to go in this direction, I think we should stick with it when we 
>>> introduce `closed`.
>>> 
>>>>  
>>>> David
>>>> 
>>>> On 9 Feb 2017, at 00:05, Matthew Johnson via swift-evolution 
>>>> <[email protected] 
>>>> <javascript:_e(%7B%7D,'cvml','[email protected]');>> 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
>>>>> [email protected] 
>>>>> <javascript:_e(%7B%7D,'cvml','[email protected]');>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> 
> 
> _______________________________________________
> swift-evolution mailing list
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to