Alternative: leave “public enum” as it is now, and spell the resilient version “@resilient enum”
Nevin On Sunday, February 12, 2017, Matthew Johnson via swift-evolution < [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) 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) 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 > > > >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
