Sent from my iPad
> On Feb 11, 2017, at 7:22 AM, Rien <[email protected]> wrote: > > 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. That is actually not a very good fit at all. `final` means a class cannot have any subclasses. If we applied it to enums I think the closest parallel would be an enum with no cases. But this makes the enum uninhabited so it is not just like `final class`, but actually more like `abstract final class`. > > 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
