> On Aug 10, 2017, at 9:25 AM, Vladimir.S <sva...@gmail.com> wrote:
> 
> On 10.08.2017 16:46, Matthew Johnson via swift-evolution wrote:
>>> On Aug 10, 2017, at 7:46 AM, James Froggatt via swift-evolution
>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>> Since it seems to have been lost in the noise, I want to second with 
>>> support for
>>> Xiaodi's syntax of having `default` appearing in the enum declaration 
>>> itself.
>>> It's much clearer in its intention, feels very ‘Swifty’, and more 
>>> importantly it
>>> doesn't prompt whole threads debating the semantics of `open` vs `public`.
>> I think Xiaodi’s syntax is very elegant if we want to avoid the access 
>> control
>> style syntax.  However, it does one problem: the “error of omission” (not 
>> thinking
>> about open vs closed) leaves a library author with a closed enum, preventing 
>> them
>> from adding cases in the future without breaking compatibility.  I’m not 
>> sure this
>> is acceptable.
> 
> Then, doesn't this mean that any 'usual' enum should be 'open' by default, 
> and only enum declared with some marker (like 'final' or 'enum(sealed)') can 
> be 'closed'?
> 
> Otherwise we need to require an explicit marker for *each* enum, and so break 
> the source compatibility? (we'll have to append that marker to each enum in 
> your current code)

This is a good point.  A good first decision is whether we prioritize source 
compatibility or the more conservative “default”.  If source compatibility is 
prioritized Xiaodi’s proposed syntax is probably hard to beat.

> 
> Also I'd suggest this for closed enum:
> 
> enum MyClosedEnum {
>  case a
>  case b
>  case c
>  final
> }
> 
> So, for public closed enum it will looks like:
> 
> public enum MyClosedEnum {
>  case a
>  case b
>  case c
>  final
> }
> 
> Also, if we need to explicitly mark open enum, probably we can consider 
> 'continue' keyword, as IMO is not clear what 'default' is saying on 
> declaration site('you must insert `default` in switch'? 'there are other 
> `default` cases'?) :
> 
> public enum MyOpenEnum {
>  case a
>  case b
>  case c
>  continue // to be continue...
> }
> 
> 
>>> ------------ Begin Message ------------ Group: 
>>> gmane.comp.lang.swift.evolution MsgID: 
>>> <CAGY80u=kVQA1q=5tmxxxfgm4tlgfuqh61en1daepemaa_fo...@mail.gmail.com>
>>> On Tue, Aug 8, 2017 at 5:27 PM, Jordan Rose via swift-evolution < 
>>> swift-evolution-m3fhrko0vlzytjvyw6y...@public.gmane.org> wrote:
>>>> 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
>>> Jordan, I'm glad you're bringing this back up. I think it's clear that 
>>> there's
>>> appetite for some forward movement in this area.
>>> With respect to syntax--which the conversation in this thread has tackled 
>>> first--I agree with the discussion that "open" and "closed" are attractive 
>>> but
>>> also potentially confusing. As discussed in earlier threads, both "open" and
>>> "closed" will constrain the enum author and/or user in ways above and beyond
>>> "public" currently does, but the terminology does not necessarily reflect 
>>> that
>>> (as open is the antonym of closed); moreover, the implications of using 
>>> these
>>> keywords with enums don't necessarily parallel the implications of using 
>>> them
>>> with classes (for example, an open class can be subclassed; an open enum 
>>> that
>>> gains additional cases is, if anything, something of a supertype of the
>>> original).
>>> I'd like to suggest a different direction for syntax; I'm putting it forward
>>> because I think the spelling itself naturally suggests a design as to which
>>> enums are (as you call it) "open" or "closed," and how to migrate existing
>>> enums:
>>> ``` enum MyClosedEnum { case a case b case c }
>>> enum MyOpenEnum { case a case b case c default } ```
>>> In words, an enum that may have future cases will "leave room" for them by 
>>> using
>>> the keyword `default`, sort of paralleling its use in a switch statement. 
>>> All
>>> existing Swift enums can therefore continue to be switched over 
>>> exhaustively;
>>> that is, this would be an additive, source-compatible change. For 
>>> simplicity, we
>>> can leave the rules consistent for non-public and public enums; or, we could
>>> prohibit non-public enums from using the keyword `default` in the manner 
>>> shown
>>> above. Obj-C enums would be imported as though they declare `default` unless
>>> some attribute like `enum_extensibility` is used to annotate them.
>>> Thoughts?
>>> ------------- End Message -------------
>>> _______________________________________________ swift-evolution mailing 
>>> list swift-evolution@swift.org 
>>> 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
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to