> On 12 Feb 2017, at 16:38, Matthew Johnson <[email protected]> wrote: > > >> On Feb 12, 2017, at 12:50 AM, David Hart <[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. >> >> 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]> 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] >>> https://lists.swift.org/mailman/listinfo/swift-evolution >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
