[swift-evolution] Handling unknown cases in enums [RE: SE-0192]
Chris Lattner
clattner at nondot.org
Tue Jan 9 00:54:18 CST 2018
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.
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.
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.
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.
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?
-Chris
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20180108/393d7a29/attachment.html>
More information about the swift-evolution
mailing list