On Sat, Feb 11, 2017 at 1:50 PM, Matthew Johnson <[email protected]> wrote:
> > 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]> > wrote: > >> >> >> Sent from my iPad >> >> On Feb 10, 2017, at 9:48 PM, Xiaodi Wu <[email protected]> wrote: >> >> On Wed, Feb 8, 2017 at 5:05 PM, 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/piper >>> mail/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/LibraryEvol >>> ution.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. > Perfect; I'm on board with that rationale. > - 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. > You're quite right. I was clearly thinking more clearly yesterday than today. > 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
