> 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