On Sat, Feb 11, 2017 at 12:42 PM, Matthew Johnson via swift-evolution < swift-evolution@swift.org> wrote:
> > > Sent from my iPad > > > On Feb 11, 2017, at 7:22 AM, Rien <r...@balancingrock.nl> 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`. > Well, you _could_ move `final` to the cases themselves, and it would mean the right thing: `enum Foo { final case bar, baz }`. > > > > 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 < > swift-evolution@swift.org> wrote: > >> > >> > >> > >> Sent from my iPad > >> > >>> On Feb 11, 2017, at 4:25 AM, Adrian Zubarev via swift-evolution < > swift-evolution@swift.org> 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 ( > swift-evolution@swift.org) schrieb: > >>> > >>>> On Wed, Feb 8, 2017 at 5:05 PM, Matthew Johnson via swift-evolution < > swift-evolution@swift.org> 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 > >>>> swift-evolution@swift.org > >>>> https://lists.swift.org/mailman/listinfo/swift-evolution > >>> > >>> _______________________________________________ > >>> swift-evolution mailing list > >>> swift-evolution@swift.org > >>> https://lists.swift.org/mailman/listinfo/swift-evolution > >> _______________________________________________ > >> swift-evolution mailing list > >> swift-evolution@swift.org > >> https://lists.swift.org/mailman/listinfo/swift-evolution > > > > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org > https://lists.swift.org/mailman/listinfo/swift-evolution >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution