Sent from my iPad
> On Feb 11, 2017, at 12:44 PM, Xiaodi Wu <[email protected]> wrote: > >> On Sat, Feb 11, 2017 at 12:42 PM, Matthew Johnson via swift-evolution >> <[email protected]> wrote: >> >> >> 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`. > > Well, you _could_ move `final` to the cases themselves, and it would mean the > right thing: `enum Foo { final case bar, baz }`. I don't quite follow this. In order for `final` to mean something when applied to a kind of entity it should also be meaningful to non-final entities of that same kind. What would a non-final case be? > >> >> > >> > 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 >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
