SE-0192 — Non-Exhaustive Enums

IMO this is still too large of a hammer for this problem.

This whole “unexpected case” thing is only a problem when you’re linking libraries that are external to/shipped independently of your app. Right now, the *only* case where this might exist is Swift on the server. We *might* run in to this in the future once the ABI stabilizes and we have the Swift libraries shipping as part of iOS/macOS/Linux. Other than this, unexpected enum cases won’t really be a problem developers have to deal with.

Because this will be such a relatively rare problem, I feel like a syntax change like what’s being proposed is a too-massive hammer for such a small nail.

What feels far more appropriate is:

:a: Teaching the compiler/checker/whatever about the linking semantics of modules. For modules that are packaged inside the final built product, there is no need to deal with any unexpected cases, because we already have the exhaustiveness check appropriate for that scenario (regardless of whether the module is shipped as a binary or compiled from source). The app author decides when to update their dependencies, and updating those dependencies will produce new warnings/errors as the compiler notices new or deprecated cases. This is the current state of things and is completely orthogonal to the entire discussion.

and

:b: Adding an attribute (@frozen, @tangled, @moana, @whatever) that can be used to decorate an enum declaration. This attribute would only need to be consulted on enums where the compiler can determine that the module will *not* be part of the final built product. (Ie, it’s an “external” module, in my nomenclature). This, then, is a module that can update independently of the final app, and therefore there are two possible cases:

  1️⃣ If the enum is decorated with @frozen, then I, as an app author, have the assurance that the enum case will not change in future releases of the library, and I can safely switch on all known cases and not have to provide a default case.

  2️⃣ If the enum is NOT decorated with @frozen, then I, as an app author, have to account for the possibility that the module may update from underneath my app, and I have to handle an unknown case. This is simple: the compiler should require me to add a “default:” case to my switch statement. This warning is produced IFF: the enum is coming from an external module, and the enum is not decorated with @frozen.

···

==========

With this proposal, we only have one thing to consider: the spelling of @frozen/@moana/@whatever that we decorate enums in external modules with. Other than that, the existing behavior we currently have is completely capable of covering the possibilities: we just keep using a “default:” case whenever the compiler can’t guarantee that we can be exhaustive in our switching.

Where the real work would be is teaching the compiler about internally-vs-externally linked modules.

Dave

On Jan 2, 2018, at 7:07 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md\]

Whew! Thanks for your feedback, everyone. On the lighter side of feedback—naming things—it seems that most people seem to like '@frozen', and that does in fact have the connotations we want it to have. I like it too.

More seriously, this discussion has convinced me that it's worth including what the proposal discusses as a 'future' case. The key point that swayed me is that this can produce a warning when the switch is missing a case rather than an error, which both provides the necessary compiler feedback to update your code and allows your dependencies to continue compiling when you update to a newer SDK. I know people on both sides won't be 100% satisfied with this, but does it seem like a reasonable compromise?

The next question is how to spell it. I'm leaning towards `unexpected case:`, which (a) is backwards-compatible, and (b) also handles "private cases", either the fake kind that you can do in C (as described in the proposal), or some real feature we might add to Swift some day. `unknown case:` isn't bad either.

I too would like to just do `unknown:` or `unexpected:` but that's technically a source-breaking change:

switch foo {
case bar:
  unknown:
  while baz() {
    while garply() {
      if quux() {
        break unknown
      }
    }
  }
}

Another downside of the `unexpected case:` spelling is that it doesn't work as part of a larger pattern. I don't have a good answer for that one, but perhaps it's acceptable for now.

I'll write up a revision of the proposal soon and make sure the core team gets my recommendation when they discuss the results of the review.

---

I'll respond to a few of the more intricate discussions tomorrow, including the syntax of putting a new declaration inside the enum rather than outside. Thank you again, everyone, and happy new year!

Jordan

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md\]

Whew! Thanks for your feedback, everyone. On the lighter side of feedback—naming things—it seems that most people seem to like '@frozen', and that does in fact have the connotations we want it to have. I like it too.

Should there be a new Clang attribute for "frozen" enums? Then the existing `__attribute__((enum_extensibility(closed)))` would only prevent "private" cases (and not future "public" cases).

<Attributes in Clang — Clang 18.0.0git documentation;

More seriously, this discussion has convinced me that it's worth including what the proposal discusses as a 'future' case. The key point that swayed me is that this can produce a warning when the switch is missing a case rather than an error, which both provides the necessary compiler feedback to update your code and allows your dependencies to continue compiling when you update to a newer SDK. I know people on both sides won't be 100% satisfied with this, but does it seem like a reasonable compromise?

The next question is how to spell it. I'm leaning towards `unexpected case:`, which (a) is backwards-compatible, and (b) also handles "private cases", either the fake kind that you can do in C (as described in the proposal), or some real feature we might add to Swift some day. `unknown case:` isn't bad either.

You might end up with `case .unknown:` and `unknown case:` in the same switch.

e.g. <https://developer.apple.com/documentation/photos/phassetmediatype&gt;

	switch mediaType {
	case .image, .video, .audio:
	    break
	case .unknown:
	    break
	unknown case:
	    break
	}

-- Ben

···

On 3 Jan 2018, at 02:07, Jordan Rose wrote:

I ended up doing these in the opposite order, writing up the new proposal first and not yet responding to the discussion that's further out. You can read my revisions at https://github.com/apple/swift-evolution/pull/777\.

In particular, I want to at least address:
- Dave D and Drew C's points about versioned libraries / linking semantics of modules.
- Jason M's point about migration
and I'll do one more pass over the thread to see if there's anything else I didn't address directly. (That doesn't mean everyone who disagrees, just messages where I think there's more I can do to explain why the proposal is the way it is.)

Jordan

P.S. Enjoying the Disney references. Thanks, Nevin and Dave. :-)

···

On Jan 2, 2018, at 18:07, Jordan Rose <jordan_rose@apple.com> wrote:

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md\]

Whew! Thanks for your feedback, everyone. On the lighter side of feedback—naming things—it seems that most people seem to like '@frozen', and that does in fact have the connotations we want it to have. I like it too.

More seriously, this discussion has convinced me that it's worth including what the proposal discusses as a 'future' case. The key point that swayed me is that this can produce a warning when the switch is missing a case rather than an error, which both provides the necessary compiler feedback to update your code and allows your dependencies to continue compiling when you update to a newer SDK. I know people on both sides won't be 100% satisfied with this, but does it seem like a reasonable compromise?

The next question is how to spell it. I'm leaning towards `unexpected case:`, which (a) is backwards-compatible, and (b) also handles "private cases", either the fake kind that you can do in C (as described in the proposal), or some real feature we might add to Swift some day. `unknown case:` isn't bad either.

I too would like to just do `unknown:` or `unexpected:` but that's technically a source-breaking change:

switch foo {
case bar:
  unknown:
  while baz() {
    while garply() {
      if quux() {
        break unknown
      }
    }
  }
}

Another downside of the `unexpected case:` spelling is that it doesn't work as part of a larger pattern. I don't have a good answer for that one, but perhaps it's acceptable for now.

I'll write up a revision of the proposal soon and make sure the core team gets my recommendation when they discuss the results of the review.

---

I'll respond to a few of the more intricate discussions tomorrow, including the syntax of putting a new declaration inside the enum rather than outside. Thank you again, everyone, and happy new year!

Isn’t this going to turn both structs and non-C-like enums into types that need to be auto-boxed (as the client won’t be able to rely on knowing the fixed size anymore)? This seems like a performance hazard.

-Kevin Ballard

···

On Dec 19, 2017, at 5:35 PM, Slava Pestov <spestov@apple.com> wrote:

On Dec 19, 2017, at 3:31 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

So I guess I’m saying, I want more thought put on the topic of whether enums defined in Swift should actually default to non-exhaustive, and I’m now leaning towards the idea that they should remain exhaustive (but Obj-C enums will still default to non-exhaustive).

This would introduce an inconsistency between enums and structs. Structs will not be fixed-contents by default (there’s a proposal coming for that), which means you will be able to add stored properties after the fact. For this reason it makes more sense to me to also make enums non-exhaustive by default so that new cases can be added.

Slava

Let me give an example. The recent discussion about filterMap aired in the discussion stage misgivings about the name; but it went to review with the name filterMap. At the review stage more misgivings were raised, the review was returned for amendment. An amended name of compactMap was put forward and this was accepted yesterday as a result of the 2nd review. I think that this shows how the process can work well. If the discussions were shut down with “already covered on Swift Evolution”, then the result wouldn’t be as good.

If an argument has been put forward on Evolution, is in the alternatives section, and people are still raising the point; it is probably safe to assume that the argument hasn’t carried the day and should be revisited.

-- Howard.

···

On 19 Dec 2017, at 6:44 pm, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Dec 19, 2017 at 11:15 PM, Howard Lovatt via swift-evolution <swift-evolution@swift.org> wrote:
As an aside: there seems to be increasingly comments about proposals that say:

  1. This was discussed at the evaluation stage and rejected.
  2. This is how it is implemented in the patch.

And other comments along those lines. Neither the pre-proposal discussions nor the proposed implementation are intended to limit the scope of the review. Therefore I don’t think people should raise this as reasons. You should remember that the process is deliberately staged this way and different people may well be commenting (in fact the process rather assumes that people in the formal review will be a wider set of people).

No, previous discussion don't limit the scope of review per se, but it's not helpful to rehash the same arguments again. We want to encourage everyone to contribute their most thought-out comments as early in the process as possible to give those who propose ideas the fullest chance to flesh out any revisions. However, everyone has finite time to contribute, and if the same discussions are merely replayed at every stage of review, then the process actively discourages thoughtful early participation. After all, why bother defending ideas at the pre-proposal stage if I'm going to have to spend the time repeating myself in a few months' time anyway?

Of course, if a wider set of people have _new_ comments, those are welcome at a later stage. But, there seems to be a sense that if _I_ haven't said something already, then _I_ should say it whether or not the same viewpoint has already been aired. In my view, such an approach should be actively discouraged for the reasons above. Although a strong consensus within the community should certainly be accounted for, this list--even at the formal review stage--doesn't even come close to approximating the community of Swift users at large. Thus, review is not a vote-counting exercise to maximize the number of people who chime in, but rather it is meant to maximize the number of ideas and perspectives that are aired. If it's already been said, it doesn't need to be said again, even if _I_ haven't said it myself.

Anyway gripe over.

Proposal link: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
What is your evaluation of the proposal?

+1/2

I only give this a half because whilst it is important I can see three issues:

  1. It doesn’t seem very Swift like to have a different rule, default non-exhaustive, for public as opposed to non-public.

  2. It doesn’t seem very Swift like to have the default the unsafe case.

  3. Other languages have better solutions - see below under other languages

Is the problem being addressed significant enough to warrant a change to Swift?

Yes, Swift ABI compatibility going forwards is important

Does this proposal fit well with the feel and direction of Swift?

No. As mentioned above different rules for public and a non-safe default don’t see that Swift like.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Both Java and Scala have a better solution. In these languages enums (Scala calls them case classes) can implement protocols and the user of an enum rarely writes a switch statement, instead they call protocol methods. Enums in these languages are a fixed set of derived classes; i.e. normal OO programming rather than functional programming, which works well in the case of wanting to expand later the number of enum cases.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Have followed the discussions. Used enums in Swift and other languages extensively.

-- Howard.

On 19 Dec 2017, at 12:58 pm, Ted Kremenek <kremenek@apple.com> wrote:

When replying, please try to keep the proposal link at the top of the message:

Proposal link: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
...
Reply text
...
Other replies
What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

Is the problem being addressed significant enough to warrant a change to Swift?

Does this proposal fit well with the feel and direction of Swift?

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Let me give an example. The recent discussion about filterMap aired in the
discussion stage misgivings about the name; but it went to review with the
name filterMap. At the review stage more misgivings were raised, the review
was returned for amendment. An amended name of compactMap was put forward
and this was accepted yesterday as a result of the 2nd review. I think that
this shows how the process can work well. If the discussions were shut down
with “already covered on Swift Evolution”, then the result wouldn’t be as
good.

If an argument has been put forward on Evolution, is in the alternatives
section, and people are still raising the point; it is probably safe to
assume that the argument hasn’t carried the day and should be revisited.

The name of `filterMap` was reviewed a second time because, if I recall,
the core team felt that there might be _additional_ ideas not yet surveyed
the first time. By contrast, if an argument has already been discussed and
is even incorporated into the text of the proposal, then I very strongly
disagree that raising the point again is to be encouraged. It's a different
matter if you have additional _evidence_ or alternative _reasoning_ to
support an argument.

···

On Wed, Dec 20, 2017 at 12:42 AM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

On 19 Dec 2017, at 6:44 pm, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Dec 19, 2017 at 11:15 PM, Howard Lovatt via swift-evolution < > swift-evolution@swift.org> wrote:

As an aside: there seems to be increasingly comments about proposals that
say:

  1. This was discussed at the evaluation stage and rejected.
  2. This is how it is implemented in the patch.

And other comments along those lines. Neither the pre-proposal
discussions nor the proposed implementation are intended to limit the scope
of the review. Therefore I don’t think people should raise this as reasons.
You should remember that the process is deliberately staged this way and
different people may well be commenting (in fact the process rather assumes
that people in the formal review will be a wider set of people).

No, previous discussion don't limit the scope of review per se, but it's
not helpful to rehash the same arguments again. We want to encourage
everyone to contribute their most thought-out comments as early in the
process as possible to give those who propose ideas the fullest chance to
flesh out any revisions. However, everyone has finite time to contribute,
and if the same discussions are merely replayed at every stage of review,
then the process actively discourages thoughtful early participation. After
all, why bother defending ideas at the pre-proposal stage if I'm going to
have to spend the time repeating myself in a few months' time anyway?

Of course, if a wider set of people have _new_ comments, those are welcome
at a later stage. But, there seems to be a sense that if _I_ haven't said
something already, then _I_ should say it whether or not the same viewpoint
has already been aired. In my view, such an approach should be actively
discouraged for the reasons above. Although a strong consensus within the
community should certainly be accounted for, this list--even at the formal
review stage--doesn't even come close to approximating the community of
Swift users at large. Thus, review is not a vote-counting exercise to
maximize the number of people who chime in, but rather it is meant to
maximize the number of ideas and perspectives that are aired. If it's
already been said, it doesn't need to be said again, even if _I_ haven't
said it myself.

Anyway gripe over.

Proposal link: GitHub - apple/swift-evolution: This maintains proposals for changes and user-visible enhancements to the Swift Programming Language.
proposals/0192-non-exhaustive-enums.md

   -

   What is your evaluation of the proposal?

   +1/2

   I only give this a half because whilst it is important I can see
   three issues:

     1. It doesn’t seem very Swift like to have a different rule,
   default non-exhaustive, for public as opposed to non-public.

     2. It doesn’t seem very Swift like to have the default the unsafe
   case.

     3. Other languages have better solutions - see below under other
   languages
   -

   Is the problem being addressed significant enough to warrant a change
   to Swift?

   Yes, Swift ABI compatibility going forwards is important
   -

   Does this proposal fit well with the feel and direction of Swift?

   No. As mentioned above different rules for public and a non-safe
   default don’t see that Swift like.
   -

   If you have used other languages or libraries with a similar feature,
   how do you feel that this proposal compares to those?

   Both Java and Scala have a better solution. In these languages enums
   (Scala calls them case classes) can implement protocols and the user of an
   enum rarely writes a switch statement, instead they call protocol methods.
   Enums in these languages are a fixed set of derived classes; i.e. normal OO
   programming rather than functional programming, which works well in the
   case of wanting to expand later the number of enum cases.
   -

   How much effort did you put into your review? A glance, a quick
   reading, or an in-depth study?
   Have followed the discussions. Used enums in Swift and other
   languages extensively.

-- Howard.

On 19 Dec 2017, at 12:58 pm, Ted Kremenek <kremenek@apple.com> wrote:

When replying, please try to keep the proposal link at the top of the
message:

Proposal link: GitHub - apple/swift: The Swift Programming Language
-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
...
Reply text
...
Other replies

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review
through constructive criticism and, eventually, determine the direction of
Swift.

When reviewing a proposal, here are some questions to consider:

   -

   What is your evaluation of the proposal?
   -

   Is the problem being addressed significant enough to warrant a change
   to Swift?
   -

   Does this proposal fit well with the feel and direction of Swift?
   -

   If you have used other languages or libraries with a similar feature,
   how do you feel that this proposal compares to those?
   -

   How much effort did you put into your review? A glance, a quick
   reading, or an in-depth study?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I thought I would add another case that isn’t possible with current syntax (so far as I’m aware). You can’t negate the comparison to do something for all cases except a particular case. You have to have an empty if block and use the else block, or have an empty case in a switch statement and use the default.

enum Enum {
  case a(param: String)
  case b(param: String)
  case c(param: String)
}

let enumeration: Enum = .a(param: "Hi")

if !(case .a = enumeration) {
  // Do something
}

— Charles

···

On Dec 20, 2017, at 9:55 AM, Kevin Nattinger via swift-evolution <swift-evolution@swift.org> wrote:

I agree this would be useful. At the moment I have to hack around it with things like `var isFoo: Bool { if case .foo = self …`* with cases I commonly need, but this is definitely a feature that has come up before and I support. It is potentially related to getting the values through an accessor, which has also come up several times.

Sidenote, your `switch` example is actually trivial with existing syntax:

switch enumeration {
case .a(.c(let param)): // or just .a(.c) if you don't need the value
    print(param)
default:
    break
}

I use this from time to time switching over, e.g., optional enums.

*: ugliest syntax ever, and it can't even be used as a standalone expression.

On Dec 20, 2017, at 8:44 AM, Ethan Diamond via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello everyone,

One major pain point I've run into with Swift is the inability to evaluate the case of an enum that has associated values in a way that just returns a bool. We've been given the ability in a switch statement:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")
switch enumeration {
    case a:
      // Do something
    case b:
      // Do something
}

We'e been given the ability in the context of an if statement:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")

if case .a = enumeration {
    // Do something
}

But without a basic was of getting a bool for if an enum is a given case, here's a list of things I can't do:

Where statements:

enum Enum {
   case a(param: Enum2)
   case b(param: Enum2)
}

enum Enum2 {
    case c(param: String)
    case d(param: String)
}

let enumeration: Enum = a(param: "Hi")
switch enumeration {
    case a(let inner) where [INNER CASE IS .c]
}

---------

Filter an array for a certain case:

Expertly explained by Erica Sadun here: Challenge: Filtering associated value enumeration arrays — Erica Sadun

---------

Nicely set a UIButton to hidden if an enum is a certain case:

enum State {
    case `default`
    case searching(results: [Result])
}

myButton.isHidden = [STATE IS .searching]

---------

I've run into this issue a ton of times because I tend to represent my views a State enums. I haven't seen anything on the board for plans for solving this issue, thought. Has there been any discussion about addressing it? Ideally I'd be able to do this:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")

case .a = enumeration // Bool
case .a(let param) = enumeration // Bool, assigns "Hi" to "param"

Thanks!
Ethan

_______________________________________________
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

I think that the main confusion here stems from the word library as we are addressing something that can be divided further (and this is IMHO as many macOS/iOS devs see it):

- libraries that come with the OS - here, it absolutely makes sense to make the enums non-exhaustive as the apps are linked against these libraries and the user installs a binary that will load these at launch and they are not bundled with the app - the developer can't control future OS releases and he wants the app to run on a future OS release.
- libraries that are bundled with the app - be it PM, CocoaPods or something else - you typically update your dependencies once in a while and they change. And you want to be notified by the compiler about possible changes - extended enums, in this case. Because let's be honest - if your app has a dozen dependencies and you come to the app after a year of no development, Swift 5 came along during that period, you want to update these libraries to Swift-5-compatible versions. And no one has the time to go through all change logs - even if they were kept up-to-date and thorough, which I can't say that I've seen in many instances.

I know that this is a limited view from the perspective of an app developer and that potentially, e.g. on Linux, there may be libraries written in Swift that you may want to install via package managers and depend on them once the ABI is stable, but the choice to make them non-exhaustive by default is not in line with everything else in Swift - everything else is generally closed by default - public (-> final in other modules), no access modified (-> internal), ...

For me, it's a -1 as it is now. I'd prefer exhaustive-by-default, ObjC/C-import non-exhaustive by default (the way ObjC classes are open by default vs. public). When it comes to the switch statement, there definitely needs to be an option to make an exhaustive switch over all compile-time-known values with a warning shall a new one be added. Without that, the code will become incredibly prone to errors and hard to maintain.

···

On Dec 20, 2017, at 9:35 PM, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

On 20. Dec 2017, at 19:54, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Dec 20, 2017, at 05:36, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 19. Dec 2017, at 23:58, Ted Kremenek via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md+1, it needs to happen (and ASAP, since it _will_ introduce source-breaking changes one way or the other).

I think non-exhaustive is the correct default. However, does this not mean that, by default, enums will be boxed because the receiver doesn’t know their potential size?

It's not always boxing, but yes, there will be more indirection if the compiler can't see the contents of the enum. (More on that below.)

That would mean that the best transition path for multi-module Apps would be to make your enums @exhaustive, rather than adding “default” statements (which is unfortunate, because I imagine when this change hits, the way you’ll notice will be complaints about missing “default” statements).

Yep, that's going to be the recommendation. The current minimal-for-review implementation does not do this but I'd like to figure out how to improve that; at the very least it might be a sensible thing to do in the migrator.

I do have some thoughts about how we could ease the transition (for this and other resilience-related changes), but it’s best to leave that to a separate discussion.

The one thing I’m still not overly fond of is the name - I would like us to keep the set of resilience/optimisation related keywords to a minimum. “exhaustive” for enums feels an awful lot like “fixed_contents” for structs - couldn’t we come up with a single name which could be used for both? I don’t think anybody’s going to want to use “exhaustive” for structs.

The core team was very focused on this too, but I contend that "exhaustive" is not about optimization and really isn't even about "resilience" (i.e. the ability to evolve a library's API while preserving binary compatibility). It's a semantic feature of an enum, much like 'open' or 'final' is for classes, and it affects what a client can or can't do with an enum. For libaries compiled from source, it won't affect performance at all—the compiler still knows the full set of cases in the current version of the library even if the programmer is forced to consider future versions.

I'm working on the fixed-contents proposal now, though it won't be ready for a while, and the same thing applies there: for structs compiled from source, the compiler can still do all the same optimizations. It's only when the library has binary compatibility concerns that we need to use extra indirection, and then "fixed-contents" becomes important. (As currently designed, it doesn't affect what clients can do with the struct at all.) This means that I don't expect a "normal" package author to write "fixed-contents" at all (however it ends up being spelled), whereas "exhaustive" is a fairly normal thing to consider whenever you make an enum public.

I hope that convinces you that "fixed-contents" and "exhaustive" don't need to have the same name. I don't think anyone loves the particular name "exhaustive", but as you see in the "Alternatives considered" we didn't manage to come up with anything significantly better. If reviewers all prefer something else we'd consider changing it.

Thanks for responding!
Jordan

When you say “libraries compiled from source”, what do you mean?

As for whether its a resilience feature: actually it is completely a resilience feature. The effects on switching are only side-effects; really what “exhaustive” or “nonexhaustive” are saying is literally that cases may be added later. Even if we added private cases, you wouldn’t need to mark those enums as specially exhaustive or not; that would be implied. It’s an accommodation for things which don’t exist yet, so really, it is all about resilience IMO.

Anyway, as I see it, library authors in general ought to be happy about this:
+ Their libraries become safer by default, so they can make changes in the future without having to worry about breakage
+ It doesn’t affect your code inside of a module, so it only affects types they already explicitly marked “public”

The only people who lose are multi-module App developers, because they are “library authors” who don’t need to care about evolution, and now need to add attributes to things they wouldn’t have to before, or suffer language and performance penalties. Their libraries become less reusable and not resilient-by-default.

For example, I have an App for which I wrote a cross-platform model framework in Swift. When I compile it as a framework inside my App, it is bundled there forever. However, I use the same code to build libraries for Linux, which I would like to ship in binary form to 3rd-parties. Am I supposed to litter my code with annotations to mark those types as final, just to make the App fast and convenient to code? What happens when I need to fix a bug and distribute an updated copy, this means the 3rd-parties need to recompile (which they won’t do…).

Typically, for such a problem, I would recommend using a static library instead. But we don’t have those, and anyway they’re not always the best thing these days. So that’s why I started a new thread about creating a “@static” import, so App developers can go back to all the conveniences they had before.

- Karl

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md+1, it needs to happen (and ASAP, since it _will_ introduce source-breaking changes one way or the other).

I think non-exhaustive is the correct default. However, does this not mean that, by default, enums will be boxed because the receiver doesn’t know their potential size?

It's not always boxing, but yes, there will be more indirection if the compiler can't see the contents of the enum. (More on that below.)

That would mean that the best transition path for multi-module Apps would be to make your enums @exhaustive, rather than adding “default” statements (which is unfortunate, because I imagine when this change hits, the way you’ll notice will be complaints about missing “default” statements).

Yep, that's going to be the recommendation. The current minimal-for-review implementation does not do this but I'd like to figure out how to improve that; at the very least it might be a sensible thing to do in the migrator.

I do have some thoughts about how we could ease the transition (for this and other resilience-related changes), but it’s best to leave that to a separate discussion.

The one thing I’m still not overly fond of is the name - I would like us to keep the set of resilience/optimisation related keywords to a minimum. “exhaustive” for enums feels an awful lot like “fixed_contents” for structs - couldn’t we come up with a single name which could be used for both? I don’t think anybody’s going to want to use “exhaustive” for structs.

The core team was very focused on this too, but I contend that "exhaustive" is not about optimization and really isn't even about "resilience" (i.e. the ability to evolve a library's API while preserving binary compatibility). It's a semantic feature of an enum, much like 'open' or 'final' is for classes, and it affects what a client can or can't do with an enum. For libaries compiled from source, it won't affect performance at all—the compiler still knows the full set of cases in the current version of the library even if the programmer is forced to consider future versions.

I'm working on the fixed-contents proposal now, though it won't be ready for a while, and the same thing applies there: for structs compiled from source, the compiler can still do all the same optimizations. It's only when the library has binary compatibility concerns that we need to use extra indirection, and then "fixed-contents" becomes important. (As currently designed, it doesn't affect what clients can do with the struct at all.) This means that I don't expect a "normal" package author to write "fixed-contents" at all (however it ends up being spelled), whereas "exhaustive" is a fairly normal thing to consider whenever you make an enum public.

I hope that convinces you that "fixed-contents" and "exhaustive" don't need to have the same name. I don't think anyone loves the particular name "exhaustive", but as you see in the "Alternatives considered" we didn't manage to come up with anything significantly better. If reviewers all prefer something else we'd consider changing it.

Thanks for responding!
Jordan

When you say “libraries compiled from source”, what do you mean?

- Other targets in your project
- Source packages built through SwiftPM / CocoaPods / Carthage / other

And I was being imprecise with the terminology, but also

- Libraries built by someone else but designed to be embedded into an app, so that there's no chance of a different version showing up at run-time.

As for whether its a resilience feature: actually it is completely a resilience feature. The effects on switching are only side-effects; really what “exhaustive” or “nonexhaustive” are saying is literally that cases may be added later. Even if we added private cases, you wouldn’t need to mark those enums as specially exhaustive or not; that would be implied. It’s an accommodation for things which don’t exist yet, so really, it is all about resilience IMO.

"Resilience", as an admittedly fuzzily-defined term in the Swift project, specifically refers to what changes can be made without breaking binary compatibility <https://github.com/apple/swift/blob/master/docs/Lexicon.rst&gt;\. It does not refer to every change you can make to a library. (For comparison, adding a field to a struct is not source-breaking in Swift. We would like to make it not ABI-breaking either; that proposal's coming soon.)

Anyway, as I see it, library authors in general ought to be happy about this:
+ Their libraries become safer by default, so they can make changes in the future without having to worry about breakage
+ It doesn’t affect your code inside of a module, so it only affects types they already explicitly marked “public”

That's the intent.

The only people who lose are multi-module App developers, because they are “library authors” who don’t need to care about evolution, and now need to add attributes to things they wouldn’t have to before, or suffer language and performance penalties. Their libraries become less reusable and not resilient-by-default.

For example, I have an App for which I wrote a cross-platform model framework in Swift. When I compile it as a framework inside my App, it is bundled there forever. However, I use the same code to build libraries for Linux, which I would like to ship in binary form to 3rd-parties. Am I supposed to litter my code with annotations to mark those types as final, just to make the App fast and convenient to code? What happens when I need to fix a bug and distribute an updated copy, this means the 3rd-parties need to recompile (which they won’t do…).

Typically, for such a problem, I would recommend using a static library instead. But we don’t have those, and anyway they’re not always the best thing these days. So that’s why I started a new thread about creating a “@static” import, so App developers can go back to all the conveniences they had before.

There won't be a perf penalty, but yes, I do expect multi-module apps to use 'exhaustive' on most of their enums, because they don't need the futureproofing. Maybe this should have been mentioned more explicitly in the proposal.

Thanks for the continued discussion!
Jordan

···

On Dec 20, 2017, at 12:35, Karl Wagner <razielim@gmail.com> wrote:

On 20. Dec 2017, at 19:54, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Dec 20, 2017, at 05:36, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 19. Dec 2017, at 23:58, Ted Kremenek via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Thanks for the links, Colin. I think neither of these approaches are appropriate for Swift, largely for the same reason: they can't be used to define a library's API. Polymorphic variants are ad-hoc union types (much like tuples are ad-hoc structs) which—apart from having other problems in Swift previously discussed on this list—means that you can't add new cases to them. That is, any API which takes `(red(r: Int) | green(g: Int) | blue(b: Int))` today can't add `alpha(a: Int)` in the future, because that would change the type.

It would change the type yes, but not in a binary incompatible way. Imagine this for the OS version, using OCaml pseudocode

type VERS = [> `10_0 | `10_1 | … | `10_13 ]

Then, next year, you’d change VERS to be,

type VERS = [> `10_0 | `10_1 | … | `10_13 | `10_14 ]

Any code that dealt with a VERS would still work, as it had to handle that it could contain other tags.

Sorry, I had this worked out in my head but then didn't put it into the email correctly! The sticking point is that Swift allows overloads by type, which means that argument types have to go into the mangled name of the function. (Another way to think of this is to say "to uniquely identify a function across releases, you must know its argument and return types as well as its full name".) That means that we can't just add a new case into the type, because it would change the function's type.

Okay, but what if VERS were an actual new type rather than just a typealias? Well, then you have a non-exhaustive enum.

(David Owens brought up on Twitter today that it could be a different kind of declaration rather than 'enum'. My response <https://twitter.com/UINT_MIN/status/943896442472620032&gt; was that that could work, but you end up in a worse case where (a) the two declarations are equivalent when they're not public, and (b) it's still easy to use the wrong one, especially going from Swift 1-4 habits.)

ML-style exceptions have the opposite problem; they are completely unconstrained and so a client can add new "cases" without the library author being prepared. (Swift's error-handling model in general behaves a lot like this, except it doesn't get ML's knowledge of which errors might be thrown.)

Yes, I was imagining that this would be for something with an OCaml type like [> ] or TOP, which I don’t believe is a thing? My OCaml-fu is quite weak.

I'd sum this up by saying Swift is differing from ML and from most other languages because it is solving a different problem. Swift is designed such that the compiler does not require whole-program knowledge to produce a correct, working, type-safe program. It will use any cross-module knowledge it can for optimization purposes, but the language semantics do not depend on it. And this is critical, as you note, for libraries with binary compatibility concerns.

That is… not different from ML? ML’s modules have precisely this properly, do they not? Or am I misunderstanding what you’re saying here.

ML modules provide separation of concerns, but as far as I know they can't actually be swapped out at runtime, and if someone were to do this they wouldn't be allowed to add a new variant to an existing datatype.

Thanks for the reply, it’s appreciated! Hope you’re well in California, envious of your weather trudging thru the snow here in NYC.

Happy holidays, Colin, and everyone else on the list. :-)

Jordan

···

On Dec 20, 2017, at 12:52, Colin Barrett <colin@springsandstruts.com> wrote:

On Dec 20, 2017, at 1:36 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

+1 on @frozen and John’s reasoning.

···

On 21. Dec 2017, at 04:32, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 20, 2017, at 10:16 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Dec 19, 2017, at 2:58 PM, Ted Kremenek via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

  • What is your evaluation of the proposal?

I am pleased with the broad strokes of this design. I have quibbles with three areas:

1. The `@exhaustive` attribute may be confusing because the term doesn't suggest versioning. My best alternative suggestion is `@frozen`, which matches existing programming terminology: something that has been frozen will not be changed in the future.

I rather like @frozen. We could use that across language features, so that we don't end up with a keyword per kind of declaration.

John.

2. I think we need some kind of `future` keyword in `switch` statements. Even though a nonexhaustive enum may gain additional cases in the future, it's still useful for the compiler to diagnose that you forgot *known* cases.

You say that "switches over non-exhaustive enums should be uncommon", and this is true for many—perhaps most—non-exhaustive enums, but there is still a large class of non-exhaustive enums which need to be switched over. These are the ones I called "category 2" in my previous email in this thread. `SKPaymentTransactionState` is the example I previously used; others might include `Stream.Status` (if not exhaustive), `CLAuthorizationStatus`, `EKParticipantType`, `PKPaymentMethodType`, and `MKMapType`. Each of these could plausibly have more cases added; each has a good reason why you might switch over cases (such as display in a user interface); and each ought to be promptly updated when a new OS version introduces new cases. Without compiler assistance, those updates won't happen.

If we plan to add private cases in a future version of Swift, `future` may not be the best keyword. `unknown`, `invalid` (or `case #invalid`), etc. may be better.

3. I am very skeptical of treating all enums as exhaustive if imported by `@testable import`. The only reason I can see not to do this is that forcing you to provide `default` might hide tests that need to be updated for new enum cases—but this is the exact problem that `future` is trying to solve. By contrast, treating them as non-exhaustive forces you to actually notice when an enum is published as nonexhaustive and consider whether that's the right approach.

None of these are showstoppers if left unaddressed, but I think the design would be better if we fixed them.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Yes. I have no idea how Swift programs currently behave when a future framework version adds a case, but I can't imagine they do anything good.

  • Does this proposal fit well with the feel and direction of Swift?

Yes, with the exception of conflating `default` and `future`, which removes useful correctness checks.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I've experienced bugs in Objective-C caused by the compiler not knowing an enum might have additional, unknown cases. Debugging them sucked.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I've participated in multiple rounds of discussion on this topic, and read the proposal top-to-bottom for this review.

--
Brent Royal-Gordon
Architechies

_______________________________________________
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

Okay, thanks Jordan. I appreciate your perspective, and I admit that the proposal is technically compelling. My criticism was from a place of respect and admiration, and while I may have missed the deeper technical points of the proposal, I’ll still caution about how this change will be received by the Swift developer community. Proposals like this one could benefit from a plain-language explanation that this (as Joe Groff explained on twitter) fixes a bug. Maybe even a few more before and after examples? That would help the proposal get ahead of any negative reaction.

Ash

···

--
Ash Furrow

On December 21, 2017 at 1:55:40 PM, Jordan Rose (jordan_rose@apple.com) wrote:

Thanks for your response, Ash. Comments inline.

On Dec 20, 2017, at 11:49, Ash Furrow via swift-evolution <swift-evolution@swift.org> wrote:

Proposal link: swift-evolution/0192-non-exhaustive-enums.md at master · apple/swift-evolution · GitHub

What is your evaluation of the proposal?

-1.

Is the problem being addressed significant enough to warrant a change to Swift?

I’m afraid not.

From my perspective as a Swift user, this change represents nontrivial language churn without providing a solution to a problem I have. The proposal doesn’t describe any benefits to me as an open source library maintainer or as a Swift developer. With earnest respect, the motivation section reads like “enums grow sometimes, but we like to exhaustively switch over them, so wouldn’t it be cool if …”, which is only a theoretical motivation. It fails to describe how and why this proposal would improve my Swift code or my experience using Swift.

This appears to be a solution to a non-existing problem. I worry that making this change will alienate developers from Swift and I caution against accepting it.

I wish it were, but unfortunately it's a very real problem. Cases are added to Cocoa all the time, and currently it is undefined behavior if one of those cases makes it into a switch defined in Swift. ("undefined behavior" = "no guarantees of memory safety, type safety, or even which functions are going to get invoked in the rest of the function") This is a terrible state of affairs that we need to do something about, and "make it a deterministic trap" isn't a good enough answer.

We also already know that we have clients that want to add new cases to Swift enums without breaking source or binary compatibility: the Apple overlays. It's true that this is more of a "theoretical motivation" for source framework authors at this time.

Does this proposal fit well with the feel and direction of Swift?

It may have at one time point in time, but not now.

The chaotic churn of the language, the syntax, and the standard library is supposed to be behind us. We need to accept that things fell into place as they did, often in imperfect ways. We probably could correct all the imperfections, but when would we ever stop? Language churn has a cost. This proposal is something that I could definitely see being a part of Swift 2 or Swift 3, but we have already decided that enums are exhaustive. This change, and changes fundamental to the cognitive model Swift programmers already have of their tool, need to be heavily weighted against language churn

There's a difference between imperfections that mean something is a bit harder to use, or not named the right thing, and imperfections that lead to crashes or core capabilities not being expressible. Being able to add cases to enums is something Cocoa developers have relied on for years in Objective-C, and so it isn't really sensible to not have this feature in Swift. I definitely wish we could have gotten to it sooner.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Scala’s match syntax bears a striking resemblance to Swift’s switch syntax; however, Scala does not require exhaustive cases. If the developer does not include a default case and none of the cases match the expression, an exception is thrown. Because of Swift’s error-handling model, I don’t know that this behaviour would be desirable either (though I will say it makes sense in Scala).

This is certainly an option; it's in the proposal under "Alternatives considered" as 'switch!' (causing a deterministic trap rather than an exception). That doesn't seem sufficient to deal with the realities of Cocoa, though.

Jordan

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read most of the proposal (okay I skimmed some of the nitty-gritty, but I read to the end of “Source compatibility”) as well as looked over the pre-review threads and skimmed GitHub pull request thread.

--
Ash Furrow

On December 19, 2017 at 5:58:14 PM, Ted Kremenek via swift-evolution (swift-evolution@swift.org) wrote:

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

Reviews are an important part of the Swift evolution process. All review feedback should be sent to the swift-evolution mailing list at:

https://lists.swift.org/mailman/listinfo/swift-evolution
or, if you would like to keep your feedback private, directly to the review manager.

When replying, please try to keep the proposal link at the top of the message:

Proposal link: swift-evolution/0192-non-exhaustive-enums.md at master · apple/swift-evolution · GitHub
...
Reply text
...
Other replies
What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

Is the problem being addressed significant enough to warrant a change to Swift?

Does this proposal fit well with the feel and direction of Swift?

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thanks,
Ted Kremenek
Review Manager
_______________________________________________
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

FWIW also +1 for @frozen
(and actually +1 for everything Brent said)

···

On 21.12.2017 14:28, Jonathan Hull via swift-evolution wrote:

+1 for @frozen

On Dec 20, 2017, at 7:16 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 19, 2017, at 2:58 PM, Ted Kremenek via swift-evolution <swift-evolution@swift.org> wrote:

  • What is your evaluation of the proposal?

I am pleased with the broad strokes of this design. I have quibbles with three areas:

1. The `@exhaustive` attribute may be confusing because the term doesn't suggest versioning. My best alternative suggestion is `@frozen`, which matches existing programming terminology: something that has been frozen will not be changed in the future.

2. I think we need some kind of `future` keyword in `switch` statements. Even though a nonexhaustive enum may gain additional cases in the future, it's still useful for the compiler to diagnose that you forgot *known* cases.

You say that "switches over non-exhaustive enums should be uncommon", and this is true for many—perhaps most—non-exhaustive enums, but there is still a large class of non-exhaustive enums which need to be switched over. These are the ones I called "category 2" in my previous email in this thread. `SKPaymentTransactionState` is the example I previously used; others might include `Stream.Status` (if not exhaustive), `CLAuthorizationStatus`, `EKParticipantType`, `PKPaymentMethodType`, and `MKMapType`. Each of these could plausibly have more cases added; each has a good reason why you might switch over cases (such as display in a user interface); and each ought to be promptly updated when a new OS version introduces new cases. Without compiler assistance, those updates won't happen.

If we plan to add private cases in a future version of Swift, `future` may not be the best keyword. `unknown`, `invalid` (or `case #invalid`), etc. may be better.

3. I am very skeptical of treating all enums as exhaustive if imported by `@testable import`. The only reason I can see not to do this is that forcing you to provide `default` might hide tests that need to be updated for new enum cases—but this is the exact problem that `future` is trying to solve. By contrast, treating them as non-exhaustive forces you to actually notice when an enum is published as nonexhaustive and consider whether that's the right approach.

None of these are showstoppers if left unaddressed, but I think the design would be better if we fixed them.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Yes. I have no idea how Swift programs currently behave when a future framework version adds a case, but I can't imagine they do anything good.

  • Does this proposal fit well with the feel and direction of Swift?

Yes, with the exception of conflating `default` and `future`, which removes useful correctness checks.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I've experienced bugs in Objective-C caused by the compiler not knowing an enum might have additional, unknown cases. Debugging them sucked.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I've participated in multiple rounds of discussion on this topic, and read the proposal top-to-bottom for this review.

--
Brent Royal-Gordon
Architechies

_______________________________________________
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

+1 kind of eye opening looking at it from that angle :/.

···

Sent from my iPhone

On 21 Dec 2017, at 18:02, Dave DeLong via swift-evolution <swift-evolution@swift.org> wrote:

I realized further why we should not implement this proposal: It forces the problem of binary compatibility on the app developer, when experience has shown us that problem is better handled by the libraries.

Binary Compatibility

“Binary compatibility” is the notion that, even if the libraries you link against change, your app will still behave as it did when it was first compiled. For Swift apps these days, we don’t really have this problem, *yet*. We do have the problem of “binary compatibility” with Apple-provided frameworks, but those are all written in Objective-C, and so the question of “Swift” binary compatibility is still up-in-the-air.

Post-ABI stability, we still won’t have much issue with swift binary compatibility on Apple platforms, because there isn’t a mechanism to ship a framework to your users independently of an app update. So from the app POV, none of this will be a problem for Apple platform developers.

It will be a problem for non-Apple platform developers. As a simple example, let’s say I write a web app in Swift, and deploy it to a server that has some built-in Swift-on-the-server libraries. Here, my Swift app is decoupled from the libraries, and either one can update independently of each other. If the server owner decides to update from Swift 6 to Swift 6.1, that’s cool. But my web app should continue to run and behave *as if* the server were still running Swift 6, because it has not been re-compiled to use Swift 6.1 features.

This is the situation today on Apple platforms. Every app deployed to an Apple device includes a little piece of information in the executable file indicating which SDK was used to compile the app. At runtime, the system frameworks read this value and then alter their behavior accordingly. This is why apps written against the iOS 9 SDK continue to work on iOS 11 devices; UIKit and friends are altering their behavior to provide iOS 9 semantics.

This is “binary compatibility”: the binary (your app) continues to be compatible with the dynamically linked frameworks present on the system, even though those frameworks may change.

When you have a setup where the frameworks do NOT provide binary compatibility, you end up in DLL Hell [1]. This is the same frustrating scenario when you’re in when you’re doing stuff with homebrew and find that this package you want has multiple dependencies, but these dependencies want different versions of the same library. [2]

Exhaustive Enums

All of the discussion around exhaustive enums has been from the point-of-view of “what should the behavior be when the libraries change”. Thinking back, I’m actually surprised this is a question at all, because Apple answered this *years* ago: THE BEHAVIOR SHOULD REMAIN UNCHANGED. It is up to the library authors to ensure that they’re doing what the compiled-and-unchanging application is expecting them to do.

This discussion around exhaustive enums is basically saying “can we force the developers to deal with binary incompatibility?”. We absolutely should not. There are far more app developers than library developers, and it would be a massively wasteful expenditure of engineering effort to force each and every app developer to deal with binary incompatibility, when the library can do it for them. That is the entire *purpose* of having libraries: abstract out a problem so that I, as an app developer, don’t have to spend the effort to do it myself.

Where We Should Go

Instead of forcing developers to deal with incompatible libraries, we should be discussing ways to make binary compatibility easier to implement in libraries.

One of the major problems I struggled with as a UIKit engineer was the presence of huge numbers of “if … else” checks in the code to deal with binary compatibility. It exploded the cyclomatic complexity of the classes and was a major source of technical debt that I struggled to not add to.

I would love to see some sort of formal API versioning that we could do instead in libraries, along with easy runtime support for checking the linked version of libraries, making it easy to strategize implementations based on version, etc.

But forcing developers to deal with binary incompatibility is a solution we’ve long known to be a bad one. We should not perpetuate that sin in Swift.

Dave

[1]: DLL Hell - Wikipedia
[2]: Dependency hell - Wikipedia

On Dec 20, 2017, at 10:23 AM, Dave DeLong via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 19, 2017, at 3:58 PM, Ted Kremenek via swift-evolution <swift-evolution@swift.org> wrote:

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

A very strong -1. I do not believe this is the appropriate solution to the problem.

• While the goal of the proposal is to ensure the correctness of *client* code, it does nothing to enforce the correctness of evolving *library* code. As a library author, I can declare a public enum as “exhaustive”, yet still add a new case in the next release. Nothing in the proposal prevents me from doing this, yet doing so would obviously break any clients of my library.

• The name “exhaustive” is misleading for uninformed library authors. An author creates an enum and then thinks “is that all of the cases? Yep! OK, it’s @exhaustive”. Then the next evolution of the library occurs, new cases arise, and now the enum isn’t exhaustive to handle the new cases. So a case gets added, and the formerly-but-not-actually-exhaustive enum is re-stamped as exhaustive, because it once again handles all known cases. “Exhaustive” is not a strong enough name. It does not contain the idea of eternal permanence. Once an enum gets branded as exhaustive and shipped as such, *it can never change*. “Exhaustive” does not imply that, and the lack of that implication will confuse library authors.

• This proposal does not address the case of “publicly exhaustive enums with private cases”. Consider NSExpression.ExpressionType: when creating NSPredicates from format strings, it is easy to create sub-expressions whose expression types are not one of the publicly listed cases. Based on the proposal, NSExpression.ExpressionType would be correctly imported as a non-exhaustive enum. HOWEVER. There is nothing *stopping* a library author from declaring a publicly exhaustive enum (like NSExpression.ExpressionType), but having private cases that get leaked (accidentally or not) past the public barrier and end up in client code. This proposal does nothing to prevent that.

The summary of these objections is this: you fundamentally cannot trust libraries that are not bundled with your application to not change in unexpected ways. Or in other words, if you don’t have the source code, you cannot trust it. And even if you do have the source code, it’s still questionable once you start bridging things in from other languages where this sort of safety is not enforced.

To summarize the summary: Leaving a judgement of “exhaustive or not” up to fallible library authors isn’t safe.

To summarize the summary of the summary: people are a problem.

Is the problem being addressed significant enough to warrant a change to Swift?

Yes, the problem is significant, but in my opinion this is the wrong answer.

Does this proposal fit well with the feel and direction of Swift?

No. Implementing this proposal would give the appearance of safety while still leaving developers subtly but dangerously vulnerable to imperfectly written libraries (ie, all of them).

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I’ve been following the email threads, and I’ve spent years as a library author, both on Apple frameworks and my own personal libraries.

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

-1

It took awhile to catch up. I understand the motivation but don't agree with the solution. The suggested proposal adds language complexity that can hopefully be solved in other ways.

In catching up I related to Howard Lovatt's response. Jordan responded:

I find it interesting that you call this the "unsafe" case. From my point of view <https://imgur.com/4OWRavD&gt;, it is very much in line with Swift's current design to have the default be "force clients to consider all possibilities" (like Optional), as well as "let library authors decide what a client should be able to rely on" (like 'open').

I don't understand this. Optional is exhaustive. What you're describing is more like Swift error handling, which is quite different and much more cumbersome in use.

This is an interesting point that I probably should have expanded more in the "comparison with other languages" section. Swift has protocols too, and you can build most of the features of enums with protocols. (Set aside the inferior pattern-matching for now; that's something we could add to Swift.) This is currently much more heavyweight than enums, but let's roll with it.

Next we have to deal with C enums, which can have "private cases" or newly-added cases. Okay, maybe those get imported as structs instead, like NS_OPTIONS. That's not too bad, is it?

Now we're back in a place where 'enum' always means "exhaustive". That's good, right? Except…now we have a kind of type where the recommendation will be "don't use these in your library's public API; they'll be stuck that way forever", a trap waiting for library authors. That's a problem C has with structs, and it's something we don't want to bring into Swift. Making a library necessarily requires more care than making an app, but we don't want there to be techniques that only make sense within a module, and are discouraged when writing a library.

So we can implement this world in Swift. I just don't think it'll be a better one. When someone makes a library, the default should be safe for them. That means that they reserve the ability to add cases, and also to make the enum "exhaustive" in the future if it turns out that's the right thing to do.

(You're free to continue to disagree with me on this; thanks for getting me to write it out.)

I don't think the default third-party library case mirrors Foundation and UIKit (and authors that provide libraries like that must take more care in general and don't require extra language sugar that benefits just them). There are plenty of library authors whose default case is: when you upgrade to use our latest version, you need to consider all added cases, as it's source-breaking. I think this is far more common than the well-kempt releases Apple shepherds through each year.

I don't think we should lose our primitive type that always requires exhaustive switching, and I don't see the need to overcomplicate the language when we already have at-hand solutions (like extensible structs). If we're worried about library authors falling into the trap of misusing enums, we should provide a better mechanism for declaring raw-representable (or similar) entities, via something like NS_RAW_REPRESENTABLE, compiler magic, and/or a hygienic macro system.

I'd also like to point out that the current state of Swift development requires frameworks separate from the app:

1. If you want to import code into a playground. Playgrounds are a celebrated form of code demonstration, but app executable code cannot run in a playground. It needs to be extracted to a framework first.

2. SPM executables cannot run tests or have test targets. Testable SPM code needs to be extracted to a framework. If you want to test your code, it needs to be in a framework.

Both cases above affect app developers that aren't necessarily in the library biz. It's not great if the default for them is enums that suddenly become non-exhaustive.

Stephen

···

On Dec 20, 2017, at 2:33 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

This feature would have to be applied not only to switching on enums, but to *every* operation on a resilient value. In practice, I think it generalizes to one of:

  1. Pass a linked-against version number to every resilient call.

  2. Have a way to fetch a substitute witness table for a given module version.

Either one of these seems like a tall order.

···

On Dec 21, 2017, at 10:02 AM, Dave DeLong via swift-evolution <swift-evolution@swift.org> wrote:

I would love to see some sort of formal API versioning that we could do instead in libraries, along with easy runtime support for checking the linked version of libraries, making it easy to strategize implementations based on version, etc.

--
Brent Royal-Gordon
Architechies

Many styles of programming can take advantage of if/else and switch/case being actual expressions (actually, these are all special cases of the very general concept of "folding"). We don't have this in Swift, and I have occasion to be bothered by this almost on a daily basis in my work, especially when I try to be "concise", something for which Swift should be champion.

The best possible outcome would be for Swift to have pattern matching as a proper evaluated expression, but I would accept a new operator that takes advantage of some custom compiler magic, because the use cases are so many and the convenience would be so great.

As Colin Barret said, this is a very popular thing in many modern languages.

Elviro

···

Il giorno 20 dic 2017, alle ore 19:32, Colin Barrett via swift-evolution <swift-evolution@swift.org> ha scritto:

This would be easily solved if pattern matching was available as an expression, such as in Haskell, OCaml / Standard ML, and Scala / Kotlin. :-)

On Dec 20, 2017, at 11:44 AM, Ethan Diamond via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello everyone,

One major pain point I've run into with Swift is the inability to evaluate the case of an enum that has associated values in a way that just returns a bool. We've been given the ability in a switch statement:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")
switch enumeration {
    case a:
      // Do something
    case b:
      // Do something
}

We'e been given the ability in the context of an if statement:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")

if case .a = enumeration {
    // Do something
}

But without a basic was of getting a bool for if an enum is a given case, here's a list of things I can't do:

Where statements:

enum Enum {
   case a(param: Enum2)
   case b(param: Enum2)
}

enum Enum2 {
    case c(param: String)
    case d(param: String)
}

let enumeration: Enum = a(param: "Hi")
switch enumeration {
    case a(let inner) where [INNER CASE IS .c]
}

---------

Filter an array for a certain case:

Expertly explained by Erica Sadun here: Challenge: Filtering associated value enumeration arrays — Erica Sadun

---------

Nicely set a UIButton to hidden if an enum is a certain case:

enum State {
    case `default`
    case searching(results: [Result])
}

myButton.isHidden = [STATE IS .searching]

---------

I've run into this issue a ton of times because I tend to represent my views a State enums. I haven't seen anything on the board for plans for solving this issue, thought. Has there been any discussion about addressing it? Ideally I'd be able to do this:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")

case .a = enumeration // Bool
case .a(let param) = enumeration // Bool, assigns "Hi" to "param"

Thanks!
Ethan

_______________________________________________
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

Thanks for your comments, Jon. Responses inline.

When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

Strong -1 as is.

I think I would support having an explicit ‘futureCase’ which is different from ‘default’. Requiring default is asking for bugs. With a separate ‘futureCase’, we would still get a compile-time error when all of the current cases aren’t handled. Note that ‘ futureCase’ is also different than ‘default’ in that it wouldn’t have to go at the bottom. It would only be triggered when the value isn’t one of the values which was known at compile time. We should also bike shed the name of ‘futureCase’ to come up with something that might allow us to make other types of extendable enums…. Maybe something like ‘unknownCase’ or ‘unexpectedCase’.

As for the issue of testing, we could add (later) a universally unexpected case that non-exhaustive enums can be set to for testing. I am not convinced that this is actually a big enough issue to warrant that though. Forcing ‘default’ is a much larger real-world problem, IMO (and this use of ‘default’ is just as untestable).

Both of these are discussed in "Alternatives considered", and they have flaws that led me to leave them out of the proposal. Do you have any ideas on how to improve on that?

What are the flaws? I saw that the ‘Future’ case can’t be tested… but using ‘default’ is just as untestable in the same way. Others have given lots of examples where it will actually be used.

I think we should reframe it so that instead of talking about exhaustive vs. non-exhaustive, all switches need to be exhaustive… but switches over these enums need to handle unexpected cases to be considered exhaustive.

I know it is marked as an “alternative”, but it seems clear to me that having an ‘unexpected’ case (which only matches cases not known at compile time) is the clear answer here. Something like 75% of responses have mentioned it.

I also dislike the name @exhaustive because I think it will cause confusion (i.e. “Of course my Enum is exhaustive”). I think we should just have @fixed for both enums and structs. The word fixed has the connotation that it shouldn’t change.

I don't think anybody loves "exhaustive", but using the same attribute for enums and structs isn't really appropriate, as discussed in my response to Karl yesterday <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20171218/042248.html&gt;\. I'm not opposed to a better name if we can gravitate towards one, though.

I really like @frozen. It has a much clearer connotation of what it means, and can be used for structs too.

Thanks,
Jon

···

On Dec 21, 2017, at 11:07 AM, Jordan Rose <jordan_rose@apple.com> wrote:

On Dec 20, 2017, at 12:46, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Dec 19, 2017, at 2:58 PM, Ted Kremenek via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Continuing on this vein, there is another huge reason this proposal is incomplete and won’t solve the problem.

This proposal, as I outlined previously, is all about binary compatibility and functional resilience in the face of changing libraries. However, it falls woefully short by only considering the binary implications of changing enums.

The problem of binary compatibility extends *far* beyond the mutation of enums between library versions. By only considering the “enum problem”, we are ignoring a much larger and worse problem that will be even easier for libraries to succumb to. It’s easy as a library author to inadvertently rename a method or change the type of a parameter, or add a parameter, or change the return type, or change the implementation semantics, or remove a type, or remove a function, in such a way that a linking app cannot handle. This is why the burden of binary compatibility rightfully rests on library authors, and not app authors.

The thing that changes has the responsibility to maintain compatibility for the thing that hasn’t changed.

We should absolutely not implement this proposal; it only addresses a single portion of a very large problem. Instead, we should figure out what the broader binary compatibility story is. If we can answer *that* and provide the necessary features to address it, then enum resilience will naturally fall out of it. Focusing on the enum problem is focusing on the wrong problem.

Dave

···

On Dec 21, 2017, at 11:02 AM, Dave DeLong <swift@davedelong.com> wrote:

I realized further why we should not implement this proposal: It forces the problem of binary compatibility on the app developer, when experience has shown us that problem is better handled by the libraries.

Binary Compatibility

“Binary compatibility” is the notion that, even if the libraries you link against change, your app will still behave as it did when it was first compiled. For Swift apps these days, we don’t really have this problem, *yet*. We do have the problem of “binary compatibility” with Apple-provided frameworks, but those are all written in Objective-C, and so the question of “Swift” binary compatibility is still up-in-the-air.

Post-ABI stability, we still won’t have much issue with swift binary compatibility on Apple platforms, because there isn’t a mechanism to ship a framework to your users independently of an app update. So from the app POV, none of this will be a problem for Apple platform developers.

It will be a problem for non-Apple platform developers. As a simple example, let’s say I write a web app in Swift, and deploy it to a server that has some built-in Swift-on-the-server libraries. Here, my Swift app is decoupled from the libraries, and either one can update independently of each other. If the server owner decides to update from Swift 6 to Swift 6.1, that’s cool. But my web app should continue to run and behave *as if* the server were still running Swift 6, because it has not been re-compiled to use Swift 6.1 features.

This is the situation today on Apple platforms. Every app deployed to an Apple device includes a little piece of information in the executable file indicating which SDK was used to compile the app. At runtime, the system frameworks read this value and then alter their behavior accordingly. This is why apps written against the iOS 9 SDK continue to work on iOS 11 devices; UIKit and friends are altering their behavior to provide iOS 9 semantics.

This is “binary compatibility”: the binary (your app) continues to be compatible with the dynamically linked frameworks present on the system, even though those frameworks may change.

When you have a setup where the frameworks do NOT provide binary compatibility, you end up in DLL Hell [1]. This is the same frustrating scenario when you’re in when you’re doing stuff with homebrew and find that this package you want has multiple dependencies, but these dependencies want different versions of the same library. [2]

Exhaustive Enums

All of the discussion around exhaustive enums has been from the point-of-view of “what should the behavior be when the libraries change”. Thinking back, I’m actually surprised this is a question at all, because Apple answered this *years* ago: THE BEHAVIOR SHOULD REMAIN UNCHANGED. It is up to the library authors to ensure that they’re doing what the compiled-and-unchanging application is expecting them to do.

This discussion around exhaustive enums is basically saying “can we force the developers to deal with binary incompatibility?”. We absolutely should not. There are far more app developers than library developers, and it would be a massively wasteful expenditure of engineering effort to force each and every app developer to deal with binary incompatibility, when the library can do it for them. That is the entire *purpose* of having libraries: abstract out a problem so that I, as an app developer, don’t have to spend the effort to do it myself.

Where We Should Go

Instead of forcing developers to deal with incompatible libraries, we should be discussing ways to make binary compatibility easier to implement in libraries.

One of the major problems I struggled with as a UIKit engineer was the presence of huge numbers of “if … else” checks in the code to deal with binary compatibility. It exploded the cyclomatic complexity of the classes and was a major source of technical debt that I struggled to not add to.

I would love to see some sort of formal API versioning that we could do instead in libraries, along with easy runtime support for checking the linked version of libraries, making it easy to strategize implementations based on version, etc.

But forcing developers to deal with binary incompatibility is a solution we’ve long known to be a bad one. We should not perpetuate that sin in Swift.

Dave

[1]: DLL Hell - Wikipedia
[2]: Dependency hell - Wikipedia

On Dec 20, 2017, at 10:23 AM, Dave DeLong via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Dec 19, 2017, at 3:58 PM, Ted Kremenek via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

A very strong -1. I do not believe this is the appropriate solution to the problem.

• While the goal of the proposal is to ensure the correctness of *client* code, it does nothing to enforce the correctness of evolving *library* code. As a library author, I can declare a public enum as “exhaustive”, yet still add a new case in the next release. Nothing in the proposal prevents me from doing this, yet doing so would obviously break any clients of my library.

• The name “exhaustive” is misleading for uninformed library authors. An author creates an enum and then thinks “is that all of the cases? Yep! OK, it’s @exhaustive”. Then the next evolution of the library occurs, new cases arise, and now the enum isn’t exhaustive to handle the new cases. So a case gets added, and the formerly-but-not-actually-exhaustive enum is re-stamped as exhaustive, because it once again handles all known cases. “Exhaustive” is not a strong enough name. It does not contain the idea of eternal permanence. Once an enum gets branded as exhaustive and shipped as such, *it can never change*. “Exhaustive” does not imply that, and the lack of that implication will confuse library authors.

• This proposal does not address the case of “publicly exhaustive enums with private cases”. Consider NSExpression.ExpressionType: when creating NSPredicates from format strings, it is easy to create sub-expressions whose expression types are not one of the publicly listed cases. Based on the proposal, NSExpression.ExpressionType would be correctly imported as a non-exhaustive enum. HOWEVER. There is nothing *stopping* a library author from declaring a publicly exhaustive enum (like NSExpression.ExpressionType), but having private cases that get leaked (accidentally or not) past the public barrier and end up in client code. This proposal does nothing to prevent that.

The summary of these objections is this: you fundamentally cannot trust libraries that are not bundled with your application to not change in unexpected ways. Or in other words, if you don’t have the source code, you cannot trust it. And even if you do have the source code, it’s still questionable once you start bridging things in from other languages where this sort of safety is not enforced.

To summarize the summary: Leaving a judgement of “exhaustive or not” up to fallible library authors isn’t safe.

To summarize the summary of the summary: people are a problem.

Is the problem being addressed significant enough to warrant a change to Swift?

Yes, the problem is significant, but in my opinion this is the wrong answer.

Does this proposal fit well with the feel and direction of Swift?

No. Implementing this proposal would give the appearance of safety while still leaving developers subtly but dangerously vulnerable to imperfectly written libraries (ie, all of them).

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I’ve been following the email threads, and I’ve spent years as a library author, both on Apple frameworks and my own personal libraries.

Dave
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
Reviews are an important part of the Swift evolution process. All review feedback should be sent to the swift-evolution mailing list at:

https://lists.swift.org/mailman/listinfo/swift-evolution
or, if you would like to keep your feedback private, directly to the review manager.

When replying, please try to keep the proposal link at the top of the message:

Proposal link: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
...
Reply text
...
Other replies
What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

+1 except for the name. @frozenExposed @fixedMembers @frozenMembers.
preferably something that aligns with the other notion of not being able to add public members to structs. This will help treat structs with static members in the same way which would be ideal. I don't think enums should have their own attitude.

Is the problem being addressed significant enough to warrant a change to Swift?

don't know. im not a library author. ill defer to other library authors.

I want to revise my review here. While I am not a library author I am a library consumer.

Having the ability treat a non exhaustive enum as exhaustive should be introduced with this. I like the idea of a
`final switch`

I think it communicate clearly that I want this to be treated as exhaustive even if it is already exhaustive. Having something like future, unknowns would be weird to me.

Another option would be being able to cast a enum as exhaustive. I am not sure how that would work. I do not like switch!

Preferably I’d like to say:

switch (@exhaustive x){...}

Would this be allowed?

let @exhaustive myEnum= x

typealias @exhaustive Y = X

if let @exhaustive x = x {
     switch x {...} // exhaustive here.
}

Could this be addressed in the proposal?

···

On Dec 22, 2017, at 8:49 AM, Cheyo Jose Jimenez <cheyo@masters3d.com> wrote:

On Dec 20, 2017, at 11:12 PM, Cheyo Jimenez <cheyo@masters3d.com> wrote:

On Dec 19, 2017, at 2:58 PM, Ted Kremenek via swift-evolution <swift-evolution@swift.org> wrote:

Does this proposal fit well with the feel and direction of Swift?

yes.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

n/a

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

followed the previous discussion. read the proposal.

Thanks,
Ted Kremenek
Review Manager
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution