Hi Chris, thank you for the new idea!

FWIW, after first reading, it looks like more elegant solution than "unknown case" in initial proposal. Swift's enums deserve powerful and flexible solution!

I really like the syntax of

switch val {
case .one: ...
case .two: ...
case #unknown: ...
}

instead of

switch val {
case .one: ...
case .two: ...
unknown case: ...
}

Also the possibility to use the #unknown in pattern matching is awesome.

Please find some comments/questions inline:

On 09.01.2018 9:54, Chris Lattner via swift-evolution wrote:
The mega-thread about SE-0192 is a bit large, and I’d like to talk about one specific point.  In the review conversation, there has been significant objection to the idea of requiring a ‘default’ for switches over enums that are non-exhaustive.

This whole discussion is happening because the ABI stability work is introducing a new concept to enums - invisible members/inhabitants (and making them reasonably prominent).  A closely related feature is that may come up in the future is "private cases”.  Private cases are orthogonal to API evolution and may even occur on one that is defined to be exhaustive.

Private cases and non-exhaustive enums affect the enum in the same way: they say that the enum can have values that a client does not know about.  Swift requires switches to process *all* of the dynamically possible values, which is why the original proposal started out with the simplest possible solution: just require ‘default' when processing the cases.


*The problems with “unknown case:”*

The popular solution to this probably is currently being pitched as a change to the proposal (https://github.com/apple/swift-evolution/pull/777) which introduces a new concept “unknown case” as a near-alias for ‘default’:
https://github.com/jrose-apple/swift-evolution/blob/60d8698d7cde2e1824789b952558bade541415f1/proposals/0192-non-exhaustive-enums.md#unknown-case

In short, I think this is the wrong way to solve the problem.  I have several 
concerns with this:

1) Unlike in C, switch is Swift is a general pattern matching facility - not a way of processing integers.  It supports recursive patterns and values, and enums are not necessarily at the top-level of the pattern. https://github.com/apple/swift/blob/master/docs/PatternMatching.rst is a document from early evolution of Swift but contains a good general introduction to this.

2) Swift also has other facilities for pattern matching, including ‘if case’.  Making switch inconsistent with them is not great.

3) As pitched, “unknown case” will match *known* cases too, which is (in my 
opinion :-) oxymoronic.

Are you saying here about situation, when new case is added in non-frozen enum, but we are compiling the old code with absent new case but with "unknown case" in switch? As stated in proposal, in this case we'll have a warning exactly for source compatibility reason: "..unknown case matches any value. ... the compiler will produce a warning if all known elements of the enum have not already been matched. This is a warning rather than an error so that adding new elements to the enum remains a source-compatible change." Do you suggest to change that warning into error?

Or you are saying about another situation when “unknown case” will match known 
cases too in the initial proposal ?



4) “unknown case:” changes the basic swift grammar (it isn’t just a modifier on case) because case *requires* a pattern.  A better spelling would be “unknown default:” which is closer to the semantic provided anyway.

5) It is entirely reasonable (though rare in practice) to want to handle 
default and unknown cases in the same switch.


Are you suggesting to have this?:

switch val {
case .one: ...
case .two: ...
case #unknown: ...
default: ..
}

So, all new cases, introduced in external module after compilation of that code will fall into "case #unknown: ..." bucket, but all "known"(at the moment of compilation) cases but not .one/.two - into "default:..." ? Personally I like that, so if I need this - I will be able to express this, but that rule seems like complicated to understand.

But this can be really helpful in situation, when you *at compilation time" thinks that you are not interested in anything but .one/.two cases of that enum, but in case of new values - you want to show(for example) a warning for app user.


For all the above reasons, ‘unknown case:' becomes a weird wart put on the side of switch/case, not something that fits in naturally with the rest of Swift.


*Alternative proposal:*

Instead of introducing a new modifier on case/switch, lets just introduce a new pattern matching operator that *matches unknown cases*, called “#unknown” or “.#unknown” or something (I’m not wed to the syntax, better suggestions welcome :).

In the simple case most people are talking about, instead of writing “unknown case:” you’d write “case #unknown:” which isn’t much different.  The nice thing about this is that #unknown slots directly into our pattern matching system.  Here is a weird example:

switch someIntEnumTuple {
case (1, .X):   … matches one combination of int and tuple...
case (2, .Y):   … matches another combination of int and tuple...
case (_, #unknown): …  matches any int and any unknown enum case ...
case default:  … matches anything ...
}

Furthermore, if you have a switch that enumerates all of the known cases and use #unknown, then it falls out of the model that new cases (e.g. due to an SDK upgrade or an updated source package) produces the existing build error.  As with the original proposal, you can always choose to use “default:” instead of “case #unknown:” if you don’t like that behavior.

Do I understand correctly, that you suggest the same behavior as in 
original(updated) proposal:
* if we have a switch on non-frozen enum value, and enumerated some cases, and have "case #unknown" - we'll have a warning(error?) if (at the moment of compilation) not all known enums are processed in "switch" ?
Just to be sure.

Btw, should this be valid? :
switch val {
  case #unknown:... // I'm only interested if new values were added to that 
external enum
  default: ... // as I understand, "default" will be required here
}


Of course, if you have an exhaustive enum (e.g. one defined in your own module or explicitly marked as such) then #unknown matches nothing, so we should warn about it being pointless.


I have some concerns(applied to the initial proposal also) about the warnings, that depend on how the enum was imported(same module / other module).

Is the warning really needed in this case? Yes, we have #unknown case, we understand that enum can(and probably will not) be changed, so the code probably will never be called. Probably we want to use the same source file with this "switch" both in separate module and in the same module. Then we'll have unnecessary warning(can't silence it) when compiling in the same module. Or we just want to write "general"("common") switch code not thinking about how enum will be imported later, so always write #unknown to complete the logic. No? At least probably we then need a way to silence the warning to keep "case #unknown" in the source in the same module with enum declaration.

Also, regarding the possible "private cases" in enum. IIUC, in case of private cases we'll need "case #unknown" in switch even for enums declared in the same module. Otherwise how we'll separate "default"(known "public" cases we are aware of but not interested in) and "private cases of that enum we know nothing about". But then how to separate "future public cases" and "private cases of enum" in switch?
Or some "case #private" will work better here? But then why "case #unknown" and not 
"case #future" ?
So, may we want to have this? :
switch extern_enum_val {
  case .one: ..
  case .two: ..

  case #private: .. // decision for some private value of enum
  case #unknown: .. // decision for some "new"(future) public value of enum
  default: .. // decision for known *at compile* public values other than 
listed above
}
Or probably I understand the concept of "private cases" wrong, then sorry.
(Sorry for such a "flow of questions" :-) Just want to understand the proposed 
solutions better)


This addresses my concerns above:

1) This fits into patterns in recursive positions, and slots directly into the existing grammar for patterns.  It would be a very simple extension to the compiler instead of a special case added to switch/case.

2) Because it slots into the pattern grammar, it works directly with 'if case’ 
and the other pattern matching stuff.

3) Doesn’t match known cases.

4) Doesn’t change the case grammar, it just adds a new pattern terminal 
production.

5) Allows weird cases like the example above.


All that said, the #unknown spelling isn’t great, but I’m sure we can find 
something else nice.

Thoughts?


Personally I like #unknown - similar like other stuff that depends on compiler magic and '#' clearly separates '#unknown' from other 'usual' cases. (Actually I like #future more, especially if we are going to have something like #private to clearly separate unknown future public cases, and unknown private cases)

Vladimir.

-Chris




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

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

Reply via email to