I think Matthew's point (with which I agree) is that, as enums are sum types, adding or removing cases is akin to subclassing. You can extend a public enum by adding methods just like you can extend a public class. But just as you cannot subclass a public class, you should not be able to add or remove cases from a public enum.
On Sat, Feb 11, 2017 at 8:37 AM, Adrian Zubarev via swift-evolution < [email protected]> wrote: > I have to correct myself here and there. > > … which would be extensible if that feature might be added to swift one > day. > > Again, I see open only as a contract to *allow* sub-typing, conformances > and overriding to the client, where extensibility of a type a story of it’s > own. > > > > -- > Adrian Zubarev > Sent with Airmail > > Am 11. Februar 2017 um 15:33:17, Adrian Zubarev ( > [email protected]) schrieb: > > It wasn’t my intention to drive to far way off topic with this. The major > point of my last bike shedding was that I have to disagree with you about > the potential future open enum vs. public enum and closed enum. > > public today does not add any guarantee to prevent the client from > extending your type. For instance: > > // Module A > public class A { public init() {} } > > // Module B > extension A { > > convenience init(foo: Int) { > print(foo) > self.init() > } > } > > That also implies to me that open as an access modifier does not prevent > extensibility. > > Speaking of opened enums, we really do not mean open enum to allow > extensibility where closed enum would mean the opposite. closed or @closed > by all the definitions I’ve read so far is what the current public means > for enums. If this is going to be fixed to closed enum (@closed public > enum) than what we’re currently speaking of is nothing else than public > enum, which would be extensible if that future might be added to swift > one day. > > Again, I see open only as a contract to prevent sub-typing, conformances > and overriding, where extensibility of a type a story of it’s own. > > Quickly compared to protocols: public-but-not-open protocol from module A > should remain extensible in module B. Consistently that would mean that public > enum is the enum when we’re talking about future extensibility of that > enum from the clients side outside your module. You simply should be able > to add new cases directly to your enum if it’s not annotated as closed. open > enum on the other hand makes only sense when we’d speak about sub-typing > on enums or value types in general. > > > -- > Adrian Zubarev > Sent with Airmail > > Am 11. Februar 2017 um 14:08:02, Matthew Johnson ([email protected]) > schrieb: > > > > 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/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'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
