> On Feb 12, 2017, at 10:24 AM, David Hart <da...@hartbit.com> wrote:
> 
> 
> On 12 Feb 2017, at 16:38, Matthew Johnson <matt...@anandabits.com 
> <mailto:matt...@anandabits.com>> wrote:
> 
>> 
>>> On Feb 12, 2017, at 12:50 AM, David Hart <da...@hartbit.com 
>>> <mailto:da...@hartbit.com>> 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 
>>> <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 
>>>> <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