> 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
