On Thu, Feb 9, 2017 at 9:57 AM, Matthew Johnson <[email protected]> wrote:
> > On Feb 8, 2017, at 5:48 PM, Xiaodi Wu <[email protected]> wrote: > > I agree very much with rationalizing access levels, but I'm not sure I > like this proposal for public vs. closed. How would the compiler stop me > from editing my own code if something is closed? The answer must be that it > can't, so I can't see it as a co-equal to open but rather simply a > statement of intention. Therefore I think use cases for the proposed > behavior of closed would be better served by annotations and proper > semantic versioning. > > > The most important point IMO is that they *are* co-equal in the sense that > they define a contract between library authors, library users and the > compiler. > Certainly, `open` and your proposed `closed` both represent contracts among library authors, users, and the compiler. But so do other features that are not access modifiers--unless your intent is to make all the (not yet totally finalized) resilience attributes such as `@inlineable` become access modifiers. That both `open` and the proposed `closed` are both contracts doesn't make them both access modifiers. To me, the reason one _could_ justify `open` being spelled like an access modifier (which I was not supportive of, actually, though I was fine with `public` not allowing subclassing) is that it quacks like an access modifier in some ways. In particular, since it offers more "access" to a class than does `public` by allowing subclassing, one can argue that it fits at the top of a hierarchy of access levels. As you define it, `closed` also makes additional guarantees to the end user than does `public` by (self-)imposing restrictions on the library author. Thus, it does not fit into a hierarchy of access modifiers where each level is more "accessible" than the next. Put another way, my point here is that `closed` is not the opposite of `open` in key ways, as the names might suggest. In fact, from the perspective of a library user, both `closed` and `open` would allow you to do more than `public`. As you note, there are some differences in how the `closed` contract is > supported. But that is far less important than the meaning of the contract > itself. > Since the additional guarantees of both `open` and your proposed `closed` impose burdens on the library _author_ and offer more flexibility to the library _user_, I feel it is highly misleading to make them co-equal but antonyms. They are not the "opposite" of each other and the spelling would be misleading. Dave's comment about tools to assist with contract-compatible API evolution > is the right way to think about this. Of course you *can* make breaking > changes, but we want to make it clear when you *are* making a breaking > change, both for source and for ABI compatibility. This will help library > authors, but it also helps users as well as the compiler reason about code > when we are able to offer stronger guarantees. > Yes, this is totally fair. > Most notably, the behavior of public enums *already* has the API contract > of `closed` and we do not want to remove that capability. This proposal > only formalizes how that contract is specified and makes it consistent > across all kinds of types. It *does not* introduce the idea of a closed > semantic contract for a type. > > As this change didn't seem in scope for Swift 4 phase 1, I've held off on > discussing my own thoughts on access levels. The idea I was going to > propose in phase 2 was to have simply open and public enums (and > protocols). I really think that completes access levels in a rational way > without introducing another keyword. > > > The reason I posted now is because formalizing this API contract for enums > must happen before ABI is locked down, and also because there is at least > one protocol in the standard library (`MirrorPath`) which is documented > with the intent that it be `closed`. > > I understand the reluctance to introduce another keyword. It isn’t clear > to me what semantics you assign to `open` and `public` enums. > > Are you suggesting that they match the semantics defined in my proposal > and suggesting closed enums (i.e. matching the current behavior of `public` > enums) would require an `@closed` annotation as suggested in the Library > Evolution document? > Yes, I am. > I am opposed to this approach because it penalizes the API contract that I > think is often the most appropriate for enums. I strongly prefer that we > adopt the same neutral stance that we when we introduced `open`. > I would not characterize `open` as a neutral stance. But that's neither here nor there. The problem (as I see it) with your argument is that, in general, the following two thoughts are incompatible: (a) the additional burden of a public API contract should be opt-in; vs. (b) there should be neutrality as to whether or not one assumes the burden of a particular public API contract. Opting in means that one has made the deliberate effort of rejecting some sort of more natural or default choice. Writing `@closed public enum` can be easily thought of as opting in, because doing so very clearly requires actively choosing to add something more than the alternative `public enum`. Writing `open class` is harder to justify as opting in, because it is not as obvious that `public class` is some sort of default. (Hence, why I did not think that `open` should have been an access level, though I was fine with the proposal otherwise.) The saving grace there is that, in the linear hierarchy of access levels, `open` is two steps away from the default access level of `internal`, whereas `public` is only one step removed. So, one can make the passable rationalization that in choosing `open` one is escalating from `internal` to `public` to `open`--i.e., that going the extra step can be regarded as the act of opting in by choosing not to stop at `public`. As I see it, one can only be said to opt in to B (vs. an alternative A) only to the extent that A and B are not neutral choices but rather in some sort of hierarchy. Again, I'm unconvinced `closed` fits into a linear hierarchy of access modifiers, and therefore I see spelling `closed` like `open` as problematic. On the other hand, you might be suggesting that `public` enums maintain > their current behavior and we simply introduce `open` as a modifier that > reserves the right for the *library* to introduce new cases while > continuing to prohibit *users* from introducing new cases. This approach > has inconsistent semantics for both `public` and `open`. These keywords > would indicate a different API contract for enums than they do for classes > and protocols. In fact, `open` for enums would have a contract analagous > with `public` for classes and protocols. This feels like a recipe for > confusion. IMO, having consistent semantics for each keyword is pretty > important. We already have, and desire to continue to have, three distinct > semantic contracts. If we want keywords with consistent semantics we are > going to have to introduce a new keyword for the third meaning. > > > On Wed, Feb 8, 2017 at 17: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
