[swift-evolution] [Accepted] SE-0155: Normalize Enum Case Representation

Goffredo Marocchi panajev at gmail.com
Thu Apr 20 16:50:30 CDT 2017


One thing I wanted to point out and that was a non trivial issue last year and that the core team did discuss and agreed to revisit (if I remember the thread update by Chris Lattner last year):

> Note that since the labels aren't part of a tuple, they no longer participate in type checking, behaving consistently with functions.
> 
> let f = Expr.elet // f has type ([(String, Expr)], Expr) -> Expr f([], anExpr) // Okay! f(locals: [], body: anExpr) // Won't compile.
I appreciate effort for consistency, but I hope this is not going to be used against the effort to bring labels in functions/stored closures/callbacks.

Swift, as we discussed last year, made a conscious, intentional effort to double down on the self documentation and call site readability of argument labels in general and the status quo after Swift 3/3.1 is not reflecting that whenever functions are stored in variables and/or passed to other functions as arguments.

I would like not to miss the Swift 4.0 deadline for this and we should discuss it sooner rather than later. I brought this up here because, for consistency, we are doubling down on something we should really be discussing to improve in my opinion.

Sent from my iPhone

> On 20 Apr 2017, at 21:39, Xiaodi Wu via swift-evolution <swift-evolution at swift.org> wrote:
> 
>> On Thu, Apr 20, 2017 at 3:20 PM, John McCall via swift-evolution <swift-evolution at swift.org> wrote:
>> Proposal Link: https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md
>> 
>> Hello Swift Community,
>> 
>> The review of SE-0155 "Normalize Enum Case Representation” ran from March 31st through April 10th, 2017. The proposal is accepted with revisions.
>> 
>> Feedback from the community was positive about most aspects of the proposal.  However, there was substantial disagreement about the right direction for pattern matching.  The core team discussed this issue in depth.
>> 
>> Pattern matching is central to the use of enum types.  It's the only way you can use an enum value, besides general operations like passing it to a function or the special affordances for Optionals.  Pattern matching is as central to enums as stored property access is to structs, and it's fair to be worried about anything that would make it substantially more onerous.  Unconditionally requiring associated-value labels in case patterns would certainly do that, and several members of the core team expressed concern that it would be bad enough to discourage the use of associated-value labels completely — in effect, subverting the entire language feature being proposed.
>> 
>> It is true that including associated-value labels in case patterns does preserve a great deal of information in the source code:
>> 
>>   - This information can usefully contribute to the clarity of the code following the pattern.
>> 
>>   - Hiding this information can lead to bugs that would be self-evident if the case labels were always included.  For example, if a case payload included a number of different boolean flags, it would be easy for a pattern to accidentally label them in the wrong order.
>> 
>>   - Finally, this information may be necessary in order to determine which case is being matched, since the proposal adds the ability to distinguish cases purely by the labels on associated values.
>> 
>> However, the core team feels that there are counter-arguments which weaken the force of these considerations:
>> 
>>   - While an associated-value label can indeed contribute to the readability of the pattern, the programmer can also choose a meaningful name to bind to the associated value.  This binding name can convey at least as much information as a label would.
>> 
>>   - The risk of mis-labelling an associated value grows as the number of associated values grows.  However, very few cases carry a large number of associated values.  As the amount of information which the case should carry grows, it becomes more and more interesting to encapsulate that information in its own struct — among other reasons, to avoid the need to revise every matching case-pattern in the program.  Furthermore, when a case does carry a significant number of associated values, there is often a positional conventional between them that lowers the risk of re-ordering: for example, the conventional left-then-right ordering of a binary search tree.  Therefore this risk is somewhat over-stated, and of course the programmer should remain free to include labels for cases where they feel the risk is significant.
>> 
>>   - It is likely that cases will continue to be predominantly distinguished by their base name alone.  Methods are often distinguished by argument labels because the base name identifies an entire class of operation with many possible variants.  In contrast, each case of an enum is a kind of data, and its name is conventionally more like the name of a property than the name of a method, and thus likely to be unique among all the cases.  Even when cases are distinguished using only associated value labels, it simply means that the corresponding case-patterns must include those labels; we should not feel required to force that burden on all other case-patterns purely to achieve consistency with this presumably-unusual style.
>> 
>> Accordingly, while it needs to be possible to include associated value labels in a case-pattern, and in some situations it may be wise to include them, the core team believes that requiring associated value labels would be unduly onerous.  Therefore, the core teams revises the proposal as follows:
>> 
>> A case pattern may omit labels for the associated values of a case if there is only one case with the same base name and arity.  A pattern must omit all labels if it omits any of them; thus, a case pattern either exactly matches the full name of a case or has no labels at all.  For example:
>> 
>>   enum E {
>>     case often(first: Int, second: Int)
>>     case lots(first: Int, second: Int)
>>     case many(value: Int)
>>     case many(first: Int, second: Int)
>>     case many(alpha: Int, beta: Int)
>>     case sometimes(value: Int)
>>     case sometimes(Int)
>>   }
>> 
>>   switch e {
>>   // Valid: the sequence of labels exactly matches a case name.
>>   case .often(first: let a, second: let b):
>>     ...
>> 
>>   // Valid: there is only one case with this base name.
>>   case .lots(let a, let b):
>>     ...
>> 
>>   // Valid: there is only one case with this base name and payload count.
>>   case .many(let a):
>>     ...
>> 
>>   // Invalid: there are multiple cases with this base name and payload count.
>>   case .many(let a, let b):
>>     ...
>> 
>>   // Valid: the sequence of labels exactly matches a case name.
>>   case .many(first: let a, second: let b):
>>     ...
>> 
>>   // Invalid: includes a label, but not on all of the labelled arguments.
>>   case .same(alpha: let a, let b):
>>     ...
>> 
>>   // Valid: the sequence of labels exactly matches a case name (that happens to not provide any labels).
>>   case .sometimes(let x):
>>     ...
>> 
>>   // Invalid: includes a label, but there is no matching case.
>>   case .sometimes(badlabel: let x):
>>     ...
>>   }
>> 
>> This only affects case patterns.  Constructing a case always requires that any associated value labels in the case name be provided.
>> 
>> A case pattern must include patterns for all associated values of the case, even if the associated value has a default value.  We may choose to relax this rule in a future release, or generally provide some sort of "..." syntax for indicating that there are associated values being ignored.
> 
> I assume that the following is obvious and the core team's intention, but it bears confirmation and being documented:
> 
> ```
> enum F {
>   case many(first: Int, second: Int)
>   case many(Int, Int)
>   case withDefaultValue(Int, Int, Int=42)
>   case withDefaultValue(first: Int, second: Int, third: Int=42)
>   case withDefaultValue(first: Int, second: Int, notThirdToMakeAPoint: Int=43)
> }
> 
> switch f {
> // Valid, even though multiple cases have this base name and arity,
> // because it is an exact match for a case that provides no labels.
> case .many(let a, let b):
>   break
> 
> // Invalid, as case pattern must include even values with default.
> case .withDefaultValue(let a, let b):
>   break
> 
> // Valid, as inclusion doesn't mean binding.
> case .withDefaultValue(let a, let b, _):
>   break
> 
> // Valid, for the same reason as above.
> case .withDefaultValue(first: let a, second: let b, third: _):
>   break
> 
> // Invalid, because the label is part of what's required, even if using `_`,
> // since otherwise it could be ambiguous as to which case is being matched.
> case .withDefaultValue(first: let a, second: let b, _):
>   break
> }
> ```
> 
> 
>> The proposal includes a rule inferring labels in case patterns from binding names.  The core team feels that imparting local variable names with this kind of significance would be unprecedented, surprising, and rather "pushy".  The goal of this rule is also largely achieved by the new rule allowing labels to be omitted regardless of binding.  Accordingly, this rule is struck from the proposal.  That said, it would be a good idea for the implementation to warn when a binding name matches the label for a different associated value.
>> 
>> John McCall
>> Review Manager
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> 
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170420/13fe903b/attachment.html>


More information about the swift-evolution mailing list