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

Reply via email to