I admit that I have not followed all of this discussion, but as far as I see, we could equally well do this by calling “not-open” enum’s “final”. It seems like a better fit to me.
Regards, Rien Site: http://balancingrock.nl Blog: http://swiftrien.blogspot.com Github: http://github.com/Balancingrock Project: http://swiftfire.nl > On 11 Feb 2017, at 14:07, Matthew Johnson via swift-evolution > <[email protected]> wrote: > > > > Sent from my iPad > > On Feb 11, 2017, at 4:25 AM, Adrian Zubarev via swift-evolution > <[email protected]> wrote: > >> I’m probably better describing things with some bikeshedding code, but feel >> free to criticize it as much as you’d like. >> >> //===========--------- Module A ---------===========// >> @closed public enum A { >> case a >> } >> >> extension A { >> case aa // error, because enum is closed >> } >> > This is an error because you can't add cases in an extension. I imagine this > is how cases would be added outside the module if we allow `open enum` in the > future. But whether or not this is allowed *within* the module is a separate > question that is orthogonal to `closed` and `open`. > > >> >> public func foo(a: A) { >> switch a { >> case .a: >> print("done") >> } >> } >> >> public enum B { >> case b >> } >> >> extension B { >> case bb // fine, because not-closed enums are extensible >> } >> > As noted above, whether this is allowed or not *within* the module is > orthogonal to `closed`. *Outside* the module it would only be possible for > enum declared `open` (if we add this feature in the future). > >> >> public func bar(b: B) { >> switch b { >> case .b: >> print("b") >> >> default: // always needed >> print("some other case") >> } >> } >> >> // Sub-enum relationships >> >> // Possible even the enum A is closed, because `@closed` only >> // closes the extensibility of an enum >> enum SubA : A { >> case aa >> } >> >> > Now you're talking about value subtypes. That is orthogonal. Also, this > syntax already has a meaning (the raw value of the enum is A) so we wouldn't > be able to use it the way you are intending here. Finally, it is misleading > syntax because what you mean here is "A is a subtype of SubA" which is > exactly the opposite of what the syntax implies. > > All values of A are valid values of SubA, but SubA has values that are not > valid values of A. > >> // The following enum can have a sub-enum in the clients module >> open enum C { >> case c >> } >> >> public func cool(c: C) { >> switch c { >> case .c: >> print("c") >> >> default: // always needed >> print("some other case") >> } >> } >> >> @closed open enum D { >> case d >> } >> >> public func doo(d: D) { >> switch b { >> case .b: >> print("b") >> } >> } >> >> // The enum case is always known at any point, no matter >> // where the instance comes from, right? >> >> let subA = SubA.aa >> let otherSubA = SubA.a // Inherited case >> >> let a: A = subA // error, downgrade the sub-enum to A first >> let a: A = otherSubA // okay >> >> foo(a: subA) // error, downgrade the sub-enum to A first >> foo(a: otherSubA) // okay >> >> //===========--------- Module B ---------===========// >> >> // Totally fine >> switch A.a { >> case .a: >> print("done") >> } >> >> extension A { >> case aa // not allowed because the enum is closed >> } >> >> extension B { >> case bbb >> } >> >> switch B.b { >> case .b: >> print("b") >> default: >> print("somethine else") >> } >> >> bar(b: B.bbb) // fine, because the switch statement on enums without >> // `@closed` has always`default` >> >> // Allowed because `C` is open, and open allows sub-typing, conforming >> // and overriding to the client >> enum SubC : C { >> case cc >> } >> >> let subC = >> SubC.cc >> >> >> cool(c: subC) // okay >> >> enum SubD : D { >> case dd >> } >> >> doo(d: D.dd)// error, downgrade sub-enum to D first >> >> My point here is, that we should not think of (possible) open enums as enums >> that the client is allowed to extend. That way we’re only creating another >> inconsistent case for the open access modifier. As far as I can tell, open >> as for today means “the client is allowed to subclass/override things from a >> different module”. >> > Yes, but subclasses are analogous to enum cases. A subtype of an enum would > remove cases. I think you are misunderstanding the relationship of enums to > classes and protocols. > >> And I already said it hundred of times that we should extend this to make >> open a true access modifier in Swift. That said the meaning of open should >> become: >> >> • The client is allowed to sub-type (currently only classes are >> supported). >> • The client is allowed to conform to open protocols >> • The client is allowed to override open type members >> This also means that extensibility is still allowed to public types. >> Public-but-not-open classes are still extensible today, which is the correct >> behavior. Extending an enum which is not closed could or probably should be >> made possible through extensions, because I cannot think of anther elegant >> way for the client to do so. >> > This is what `open enum` would allow. It is the proper enum analogue of open > classes. > >> That will leave us the possibility to think of sub-typing enums in the >> future (I sketched it out a little above). >> > Value subtyping is very interesting. I have been working on some ideas > around this but I want to keep this thread focused. > >> If I’m not mistaken, every enum case is known at compile time, >> > This is true today but will not always be true in the future. That is in > large part what this thread is about. > >> which means to me that we can safely check the case before allowing to >> assign or pass an instance of a sub-enum to some of its super-enum. >> (Downgrading an enum case means that you will have to write some code that >> either mutates your current instance or creates a new one which matches one >> of the super-enum cases.) Furthermore that allows a clear distinction of >> what open access modifier does and how @closed behaves. >> > I'm not going to comment on the rest because it is premised on a > misunderstanding of what value subtyping is. I'm going to share some ideas > around value subtyping in a new thread as soon as I have a chance to finish > putting them together. > >> To summarize: >> >> • @closed enum - you’re not allowed to add new cases to the enum in >> your lib + (you’re allowed to create sub-enums) >> • @closed public enum - you and the client are not allowed to add new >> cases (+ the client is not allowed to create sub-enums) >> • @closed open enum - you and the client are not allowed to add new >> cases (+ the client might create new sub-enums) >> • enum - you’re allowed to add new cases (default is needed in switch >> statements) (+ you can create new sub-enums) >> • public enum - you and the client are allowed to add new cases (+ only >> you are allowed to create new sub-enums) >> • open enum - you and the client are allowed to add new cases (everyone >> can create new sub-enums) >> This is a lot of bike shedding of mine, and the idea might not even see any >> light in Swift at all, but I’d like to share my ideas with the community. >> Feel free to criticize them or flesh something out into something real. :) >> >> P.S.: If we had something like this: >> >> @closed enum X { >> case x, y >> func foo() { >> switch self { >> case .x, .y: >> print("swift") >> } >> } >> >> enum Z : X { >> case z, zz >> override func foo() { >> // Iff `self` is `z` or `zz` then calling super will result in an >> error. >> // Possible solution: always tell the client to downgrade explicitly >> the >> // case first if there is an attempt to call super (if mutating), >> // or handle all cases >> >> switch self { >> case .z, .zz: >> print("custom work") >> default: // or all super-enum cases >> super.foo() >> } >> } >> } >> >> >> >> >> -- >> Adrian Zubarev >> Sent with Airmail >> >> Am 11. Februar 2017 um 04:49:11, Xiaodi Wu via swift-evolution >> ([email protected]) schrieb: >> >>> 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/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. >>> >>> 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'll leave aside closed protocols, which as per Jordan Rose's feedback >>> may or may not have sufficient interestingness. >>> - 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`. >>> 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. >>> >>> _______________________________________________ >>> 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 > _______________________________________________ > 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
