> On 10 Aug 2017, at 02:42, Jordan Rose <jordan_r...@apple.com> wrote:
> 
> :-) As you've all noted, there are some conflicting concerns for the default:
> 
> - Source compatibility: the existing behavior for an unannotated enum is 
> "closed".
> - Intuition: if you show someone an enum without an explicit annotation, 
> they'll probably expect they can switch over it. (I'm going to say this is 
> why Zach calls it a "sensible default".)
> - Consistency: switches on an enum in the same module can always be 
> exhaustive, so having it be different across modules is a bit annoying. (But 
> 'public' already acts like this.)
> 
> vs.
> 
> - Library evolution: the default should promise less, so that you have the 
> opportunity to change it.
> - Flexibility: you can emulate an exhaustive switch with a non-exhaustive 
> switch using fatalError, but not the other way around.
> 
> All of this is why I suggested it be an explicit annotation in either 
> direction, but Matthew brought up the "keyword soup" problem—if you have to 
> write (say) "public finite enum" and "public infinite enum", but would never 
> write "private finite enum" or "private infinite enum", something is 
> redundant here. Still, I'm uncomfortable with the default case being the one 
> that constrains library authors, so at least for binary frameworks (those 
> compiled "with resilience") I would want that to be explicit. That brings us 
> to one more concern: how different should binary frameworks be from source 
> frameworks?

In terms of intuition and consistency, I think we should really try to learn 
from the simplicity of public/open:

* When internal, classes are sub-classable by default for convenience, but can 
be closed with the final keyword
* When public, classes are closed to sub-classing for safety, but can be opened 
up with the open keyword (which implies public).

If we try to mirror this behaviour (the keywords are just suggestions, not 
important):

* When internal, enums are exhaustive by default for convenience, but can be 
opened-up with the partial keyword
* When public, enums are non-exhaustive by default for safety, but can be made 
exhaustive with the exhaustive keyword (which implies public).

David.

> Jordan
> 
> 
> 
>> On Aug 9, 2017, at 08:19, Zach Waldowski via swift-evolution 
>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>> 
>> I disagree. Closed is indeed the stronger guarantee, but APIs are designed 
>> differently in Swift; closed is a sensible default. We shouldn’t need to 
>> define new keywords and increase the surface area of the language for 
>> something that has verisimilitude with the existing open syntax.
>> 
>> Sincerely,
>>   Zachary Waldowski
>>   z...@waldowski.me <mailto:z...@waldowski.me>
>> 
>> 
>> On Wed, Aug 9, 2017, at 06:23 AM, David Hart via swift-evolution wrote:
>>> 
>>> 
>>> On 9 Aug 2017, at 09:21, Adrian Zubarev via swift-evolution 
>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>>> Hi Jordan, is that only me or haven't you metioned the default should be 
>>>> applied to all new enums? Personally I'd say that 'closed' should be the 
>>>> default and the 'open' enum would require an extra keyword.
>>> 
>>> I think it should definitely be the other way round for public enums 
>>> because closed is the stronger guarantee. Final is the default for classes 
>>> because open is the stronger guarantee. That’s probably why we should not 
>>> use the same keywords.
>>> 
>>>> Now about the keyword itself. Here are two keywords that IMHO nail their 
>>>> behavior down to the point:
>>>> 
>>>> finite enum A {} - so to say a closed enum (default)
>>>> infinite enum B {} - so to say an open enum (requires default case in a 
>>>> switch statement)
>>>> 
>>>> If you think the default should be the other way around, than feel free to 
>>>> switch that. 'finite' also implies that the enum connot ever be extended 
>>>> with more cases (to become infinite), which was also mentioned in your 
>>>> email.
>>>> 
>>>> -- 
>>>> Adrian Zubarev
>>>> Sent with Airmail
>>>> Am 9. August 2017 um 00:27:53, Jordan Rose via swift-evolution 
>>>> (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:
>>>> 
>>>>> 
>>>>> 
>>>>> 
>>>>> Hi, everyone. Now that Swift 5 is starting up, I'd like to circle back to 
>>>>> an issue that's been around for a while: the source compatibility of 
>>>>> enums. Today, it's an error to switch over an enum without handling all 
>>>>> the cases, but this breaks down in a number of ways: 
>>>>> 
>>>>> - A C enum may have "private cases" that aren't defined inside the 
>>>>> original enum declaration, and there's no way to detect these in a switch 
>>>>> without dropping down to the rawValue.
>>>>> - For the same reason, the compiler-synthesized 'init(rawValue:)' on an 
>>>>> imported enum never produces 'nil', because who knows how anyone's using 
>>>>> C enums anyway?
>>>>> - Adding a new case to a Swift enum in a library breaks any client code 
>>>>> that was trying to switch over it.
>>>>> 
>>>>> (This list might sound familiar, and that's because it's from a message 
>>>>> of mine on a thread started by Matthew Johnson back in February called 
>>>>> "[Pitch] consistent public access modifiers". Most of the rest of this 
>>>>> email is going to go the same way, because we still need to make progress 
>>>>> here.)
>>>>> 
>>>>> At the same time, we really like our exhaustive switches, especially over 
>>>>> enums we define ourselves. And there's a performance side to this whole 
>>>>> thing too; if all cases of an enum are known, it can be passed around 
>>>>> much more efficiently than if it might suddenly grow a new case 
>>>>> containing a struct with 5000 Strings in it.
>>>>> 
>>>>> 
>>>>> Behavior
>>>>> 
>>>>> I think there's certain behavior that is probably not terribly 
>>>>> controversial:
>>>>> 
>>>>> - When enums are imported from Apple frameworks, they should always 
>>>>> require a default case, except for a few exceptions like NSRectEdge. 
>>>>> (It's Apple's job to handle this and get it right, but if we get it wrong 
>>>>> with an imported enum there's still the workaround of dropping down to 
>>>>> the raw value.)
>>>>> - When I define Swift enums in the current framework, there's obviously 
>>>>> no compatibility issues; we should allow exhaustive switches.
>>>>> 
>>>>> Everything else falls somewhere in the middle, both for enums defined in 
>>>>> Objective-C:
>>>>> 
>>>>> - If I define an Objective-C enum in the current framework, should it 
>>>>> allow exhaustive switching, because there are no compatibility issues, or 
>>>>> not, because there could still be private cases defined in a .m file?
>>>>> - If there's an Objective-C enum in another framework (that I built 
>>>>> locally with Xcode, Carthage, CocoaPods, SwiftPM, etc.), should it allow 
>>>>> exhaustive switching, because there are no binary compatibility issues, 
>>>>> or not, because there may be source compatibility issues? We'd really 
>>>>> like adding a new enum case to not be a breaking change even at the 
>>>>> source level.
>>>>> - If there's an Objective-C enum coming in through a bridging header, 
>>>>> should it allow exhaustive switching, because I might have defined it 
>>>>> myself, or not, because it might be non-modular content I've used the 
>>>>> bridging header to import?
>>>>> 
>>>>> And in Swift:
>>>>> 
>>>>> - If there's a Swift enum in another framework I built locally, should it 
>>>>> allow exhaustive switching, because there are no binary compatibility 
>>>>> issues, or not, because there may be source compatibility issues? Again, 
>>>>> we'd really like adding a new enum case to not be a breaking change even 
>>>>> at the source level.
>>>>> Let's now flip this to the other side of the equation. I've been talking 
>>>>> about us disallowing exhaustive switching, i.e. "if the enum might grow 
>>>>> new cases you must have a 'default' in a switch". In previous (in-person) 
>>>>> discussions about this feature, it's been pointed out that the code in an 
>>>>> otherwise-fully-covered switch is, by definition, unreachable, and 
>>>>> therefore untestable. This also isn't a desirable situation to be in, but 
>>>>> it's mitigated somewhat by the fact that there probably aren't many 
>>>>> framework enums you should exhaustively switch over anyway. (Think about 
>>>>> Apple's frameworks again.) I don't have a great answer, though.
>>>>> 
>>>>> For people who like exhaustive switches, we thought about adding a new 
>>>>> kind of 'default'—let's call it 'unknownCase' just to be able to talk 
>>>>> about it. This lets you get warnings when you update to a new SDK, but is 
>>>>> even more likely to be untested code. We didn't think this was worth the 
>>>>> complexity.
>>>>> 
>>>>> Terminology
>>>>> 
>>>>> The "Library Evolution 
>>>>> <http://jrose-apple.github.io/swift-library-evolution/>" doc (mostly 
>>>>> written by me) originally called these "open" and "closed" enums 
>>>>> ("requires a default" and "allows exhaustive switching", respectively), 
>>>>> but this predated the use of 'open' to describe classes and class 
>>>>> members. Matthew's original thread did suggest using 'open' for enums as 
>>>>> well, but I argued against that, for a few reasons:
>>>>> 
>>>>> - For classes, "open" and "non-open" restrict what the client can do. For 
>>>>> enums, it's more about providing the client with additional 
>>>>> guarantees—and "non-open" is the one with more guarantees.
>>>>> - The "safe" default is backwards: a merely-public class can be made 
>>>>> 'open', while an 'open' class cannot be made non-open. Conversely, an 
>>>>> "open" enum can be made "closed" (making default cases unnecessary), but 
>>>>> a "closed" enum cannot be made "open".
>>>>> 
>>>>> That said, Clang now has an 'enum_extensibility' attribute that does take 
>>>>> 'open' or 'closed' as an argument.
>>>>> 
>>>>> On Matthew's thread, a few other possible names came up, though mostly 
>>>>> only for the "closed" case:
>>>>> 
>>>>> - 'final': has the right meaning abstractly, but again it behaves 
>>>>> differently than 'final' on a class, which is a restriction on code 
>>>>> elsewhere in the same module.
>>>>> - 'locked': reasonable, but not a standard term, and could get confused 
>>>>> with the concurrency concept
>>>>> - 'exhaustive': matches how we've been explaining it (with an "exhaustive 
>>>>> switch"), but it's not exactly the enum that's exhaustive, and it's a 
>>>>> long keyword to actually write in source.
>>>>> 
>>>>> - 'extensible': matches the Clang attribute, but also long
>>>>> 
>>>>> 
>>>>> I don't have better names than "open" and "closed", so I'll continue 
>>>>> using them below even though I avoided them above. But I would really 
>>>>> like to find some.
>>>>> 
>>>>> 
>>>>> Proposal
>>>>> 
>>>>> Just to have something to work off of, I propose the following:
>>>>> 
>>>>> 1. All enums (NS_ENUMs) imported from Objective-C are "open" unless they 
>>>>> are declared "non-open" in some way (likely using the enum_extensibility 
>>>>> attribute mentioned above).
>>>>> 2. All public Swift enums in modules compiled "with resilience" (still to 
>>>>> be designed) have the option to be either "open" or "closed". This only 
>>>>> applies to libraries not distributed with an app, where binary 
>>>>> compatibility is a concern.
>>>>> 3. All public Swift enums in modules compiled from source have the option 
>>>>> to be either "open" or "closed".
>>>>> 4. In Swift 5 mode, a public enum should be required to declare if it is 
>>>>> "open" or "closed", so that it's a conscious decision on the part of the 
>>>>> library author. (I'm assuming we'll have a "Swift 4 compatibility mode" 
>>>>> next year that would leave unannotated enums as "closed".)
>>>>> 5. None of this affects non-public enums.
>>>>> 
>>>>> (4) is the controversial one, I expect. "Open" enums are by far the 
>>>>> common case in Apple's frameworks, but that may be less true in Swift.
>>>>> 
>>>>> 
>>>>> Why now?
>>>>> 
>>>>> Source compatibility was a big issue in Swift 4, and will continue to be 
>>>>> an important requirement going into Swift 5. But this also has an impact 
>>>>> on the ABI: if an enum is "closed", it can be accessed more efficiently 
>>>>> by a client. We don't have to do this before ABI stability—we could 
>>>>> access all enums the slow way if the library cares about binary 
>>>>> compatibility, and add another attribute for this distinction later—but 
>>>>> it would be nice™ (an easy model for developers to understand) if "open" 
>>>>> vs. "closed" was also the primary distinction between "indirect access" 
>>>>> vs. "direct access".
>>>>> 
>>>>> I've written quite enough at this point. Looking forward to feedback!
>>>>> Jordan
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>> 
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org <mailto: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