> On Feb 11, 2017, at 12:40 PM, Xiaodi Wu <[email protected]> wrote: > > On Sat, Feb 11, 2017 at 6:41 AM, Matthew Johnson <[email protected] > <mailto:[email protected]>> wrote: > > > Sent from my iPad > > On Feb 10, 2017, at 9:48 PM, Xiaodi Wu <[email protected] > <mailto:[email protected]>> wrote: > >> On Wed, Feb 8, 2017 at 5:05 PM, Matthew Johnson via swift-evolution >> <[email protected] <mailto:[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. >> >> A different line of feedback here: >> >> As per previous reply, I now think if we clarify the mental model of the >> access modifier hierarchy you're proposing and adopt or reject with that >> clarity, we'll be fine whether we go with `closed` or with `@closed`. But I >> don't think the source compatibility strategy you list is the most simple or >> the most easy to understand for end users. > > I'm pretty neutral on what kind of source compatibility strategy we would > adopt. I am happy to defer to the community and core team. > >> >> - I'll leave aside closed protocols, which as per Jordan Rose's feedback may >> or may not have sufficient interestingness. > > Jordan supported allowing protocols to have the same choice of contract that > classes do today. `public protocol` has the same meaning as `open class` > today so if we want consistency we need a breaking change. > > Sure; I was specifically considering the phased introduction of `closed`. > It's been a while since I've thought about how to phase in a change regarding > public protocols and open protocols. > > That said, others make good points about _conforming to_ protocols by a type > vs. _refining_ protocols by another protocol, and whether either or both of > these is more akin to subclassing a class.
This is something that was in the back of my mind for months (I’ve thought about this off and on since last summer). My conclusion is that *conforming* is the important relationship, at least in terms of the `open`, and `closed` discussion. As I mentioned in my reply to Karl, I can’t think of any benefit that would be afforded to either a library or its clients by restricting refinement. Obviously clients get more flexibility if they *can* refine protocols defined by a library. From the perspective of a library author nothing changes if a client refines a protocol it defines. All of the semantics of the code in the library is identical either way, as is it’s options for future evolution. > >> - With respect to enums, I don't think we need such a drastic whiplash in >> terms of what will compile in future versions. Instead, we could take a more >> pragmatic approach: >> >> 1. In Swift 4, remove the warning (or is it error?) about `default` cases in >> switch statements over public enums. Simultaneously, add `closed` or >> `@closed` (whatever is the approved spelling) and start annotating standard >> library APIs. The annotation will be purely future-proofing and have no >> functional effect (i.e. the compiler will do nothing differently for a >> `closed enum` or `@closed public enum` (as the case may be) versus a plain >> `public enum`). >> 2. In Swift 4.1, _warn_ if switch statements over public enums don't have a >> `default` statement: offer a fix-it to insert `default: fatalError()` and, >> if the enum is in the same project, offer a fix-it to insert `closed` or >> `@closed`. > > Why do you say "if the enum is in the same project, offer a fix-it to insert > `closed`? If the enum is in the same project we can perform an exhaustive > switch regardless of its public API contract (except for `open` enums if we > decide to add those). > > Hmm, well now I'm not in favor of my own suggestion. A public enum, though it > may gain or lose cases in future versions, can be exhaustively switched over > in the present whether it's same-module or third-party. No warning or error > should issue on attempting to switch over a public enum without a default > case. This is true for the current semantics of `public enum`. But what I am suggesting is that this semantic be called `closed enum`. `public enum` would allow libraries to add new cases resiliently. This is the same semantic for `public enum` that is mentioned in the Library Evolution document (which spells my `closed enum` as `@closed public enum`). We have to require a default case for resilient enums because the client may run against a future version of the library with a new case. I think a couple people have mentioned either allowing an implicit default case with `break` or `fatalError` to be synthesized but I am strongly opposed to this. The only other option is a compiler error for a switch over a resilient enum that does not have a default clause. > >> 3. In Swift 5, upgrade the warning to an error for non-exhaustiveness if a >> switch statement over a public enum doesn't have a `default` statement. Now, >> new syntax to extend an `open enum` can be introduced and the compiler can >> treat closed and public enums differently. > > If the community and core team support this strategy I will also. It seems > reasonable and speeds up the transition by using the point release. That's a > great idea! >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
