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`.

> 
> 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

Reply via email to