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

Reply via email to