Sent from my iPad
> On Feb 11, 2017, at 12:30 PM, Xiaodi Wu <xiaodi...@gmail.com> wrote: > > 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. Yes, this is exactly the point I am making. I am working on writing up some ideas around value subtyping as well. I think (hope) that document will make the similarity of cases and subclasses more clear for those who don't quite see it yet. > > >> On Sat, Feb 11, 2017 at 8:37 AM, Adrian Zubarev via swift-evolution >> <swift-evolution@swift.org> 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 >> (adrian.zuba...@devandartist.com) 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 (matt...@anandabits.com) >>> schrieb: >>> >>>> >>>> >>>> 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