> On Sep 8, 2017, at 2:17 PM, Robert Widmann <devteam.cod...@gmail.com> wrote:
> 
>> 
>> On Sep 4, 2017, at 11:35 AM, Matthew Johnson via swift-evolution 
>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>> 
>> 
>>> On Sep 4, 2017, at 11:47 AM, T.J. Usiyan <griotsp...@gmail.com 
>>> <mailto:griotsp...@gmail.com>> wrote:
>>> 
>>> I wasn't arguing for a strictly parallel syntax. I was arguing against 
>>> being able to omit labels. I don't view those as strictly tied together. 
>>> How are they?
>> 
>> Like Xiaodi I don’t think it would be productive to rehash the prior 
>> discussion so I’m going to try to be brief.  
>> 
>> In the discussion one idea that arose was to support two labels for 
>> associated values in a manner similar to parameters.  One would be used 
>> during construction and the other during matching.  
>> 
>> The idea behind this was that when creating a value a case is analagous to a 
>> factory method and it would be nice to be able provide labels using the same 
>> naming guidelines we use for external argument labels.  For example, if an 
>> associated value was an index `at` might be used for clarity at the call 
>> site.  Labels like this don’t necessarily make as much sense when 
>> destructuring the value.  The idea of the “internal” label of a case was 
>> that it would be used when matching and could be elided if the bound name 
>> was identical.  In the example, `index` might be used.
> 
> It’s an interesting idea, but I don’t know of too many cases where I wouldn’t 
> want the name for destructuring to serve as an API name somehow.  A function 
> may use labels to aid understanding, flow,  readability, etc. but an enum 
> case is not necessarily a function-like value, nor does this proposal want 
> them to be (modulo some internal modeling).

An enum case is *exactly* analogous to a static factory method or property when 
it is used to construct values.  It obviously plays a different role in pattern 
context.

> 
>> 
>> When matching, `let` is interspersed between the label and the name binding.
> 
> Good thing this works then
> 
> enum Foo {
>   case foo(x: Int, y: String, z: Float)
> }
> 
> func bar(_ x : Foo) {
>   switch x {
>   case let .foo(x: x, y: y, z: z): break
>   }
> }

I consider this syntax to be an anti-pattern.  It can be unclear where a new 
name is bound and where the value of a pre-existing name is matched.

> 
> Even better, John mentioned in the rationale that if the labels ever grow to 
> be clumsy we can come up with some kind of “ellipses-like” pattern to 
> indicate we intend to match all the labelled values as they are so named, 
> etc.  

It sounds like this would introduce name bindings without the name being 
explicitly declared.  That works fine where there is a standard pattern such as 
`oldValue` in a property observer.  I’m not sure I would like it in this 
context though.

> Or, and this is the far easier thing that can and should be done today, just 
> use a struct.

I generally agree with this advice.

> 
>>  Any label is already at a distance from the name it labels.  Instead of 
>> providing a label the important thing is that the semantic of the bound 
>> variable be clear at the match site.  Much of the time the label actually 
>> reduces clarity at a match site by adding verbosity and very often 
>> repetition.  If the bound name clearly communicates the purpose of the 
>> associated value a label cannot add any additional clarity, it can only 
>> reduce clarity.
> 
> I disagree.  This would make sense in a world where we didn’t allow 
> overloading.  But for the purpose of disambiguation, this kind of logic 
> breaks down.  Say we have this construction
> 
> func bar(_ x : Foo) {
>   switch x {
>   case let .foo(x, y, z): break
>   case let .foo(x, y, z, w): break
>   }
> }
> 
> Without the definition of the original enum, could you tell me what each of 
> these cases were for, and why they were named so similarly?  Eliding the 
> label does not enable clarity, it saves keystrokes and enables ambiguous 
> patterns.

As stated above, I strongly dislike the syntax that distributes the `let` as I 
find *that* to be unclear.  Let’s rewrite that example:

func bar(_ x : Foo) {
  switch x {
  case .foo(let x, let y, let z): break
  case .foo(let x, let y, let z, let w): break
  }
}

Now I can see exactly where names are being bound.  I know if a label exists it 
matches the name that is bound.  If two distinct cases might be matched I would 
expect a compiler error.  For example, if Foo was defined as follows the above 
switch to produce an error on the second pattern but not the first:

enum Foo {
  case foo(x: Int, y: String, z: Float)
  case foo(x: Int, y: String, z: Float, s: String)
  case foo(x: Int, y: String, z: Float, w: Double)
}

If the proposal had been accepted without the modification I would not find the 
above switch ambiguous in behavior although I admit that it carries more 
potential for mistake than a design that requires explicit labels to 
disambiguate an overloaded base name.

> 
>> 
>> The proposal acknowledges most of this by allowing us to elide labels when 
>> the bound name matches the label. 
> 
> That part of the proposal was not accepted which is part of why I’m bringing 
> this up at all.  

I thought the part that elides labels was accepted with the modification that 
this only applies when the base name is unambiguous.  I suspect overloaded base 
names will be relatively rare so I think the case of unambiguous base names is 
by far the most important.  I think the rationale given is pretty good.

> Besides, it wouldn’t have worked quite the way the authors intended.
> 
> enum Foo {
>   case foo(x: Int, x: String)
> }
> 
> func bar(_ x : Foo) {
>   switch x {
>   // We wanted to avoid labels, but instead we would be required to redeclare
>   // 'x' in this pattern which forces the use of labels to allow a different 
> bound name.
>   case let .foo(x, x): break
>  }
> }  

This would of course need to be rejected because it attempts to bind the same 
name twice.  I don’t think anyone intended for this to work.

> 
> ~Robert Widmann
> 
>> 
>>> 
>>> On Mon, Sep 4, 2017 at 12:38 PM, Matthew Johnson <matt...@anandabits.com 
>>> <mailto:matt...@anandabits.com>> wrote:
>>> 
>>>> On Sep 4, 2017, at 10:52 AM, T.J. Usiyan via swift-evolution 
>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>>> 
>>>> While re-litigating has it's issues, I am for simplifying the rule and 
>>>> always requiring the labels if they exist. This is similar to the change 
>>>> around external labels. Yes, it is slightly less convenient, but it 
>>>> removes a difficult to motivate caveat for beginners.
>>> 
>>> I disagree.  Creating a value and destructuring it are two very different 
>>> operations and I believe it is a mistake to require them to have parallel 
>>> syntax.  
>>> 
>>> Imagine a future enhancement to the language that supports destructuring a 
>>> struct.  A struct might not have a strictly memberwise initializer.  It 
>>> might not even be possible to reconstruct initializer arguments for the 
>>> sake of parallel destructuring syntax.  There might even be more than one 
>>> projection that is reasonable to use when destructuring the value in a 
>>> pattern (such as cartesian and polar coordinates).
>>> 
>>> FWIW, I made this case in more detail during the discussion and review of 
>>> this proposal.
>>> 
>>>> 
>>>> On Sun, Sep 3, 2017 at 4:35 PM, Xiaodi Wu via swift-evolution 
>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>>> The desired behavior was the major topic of controversy during review; I’m 
>>>> wary of revisiting this topic as we are essentially relitigating the 
>>>> proposal.
>>>> 
>>>> To start off, the premise, if I recall, going into review was that the 
>>>> author **rejected** the notion that pattern matching should mirror 
>>>> creation. I happen to agree with you on this point, but it was not the 
>>>> prevailing argument. Fortunately, we do not need to settle this to arrive 
>>>> at some clarity for the issues at hand.
>>>> 
>>>> From a practical standpoint, a requirement for labels in all cases would 
>>>> be much more source-breaking, whereas the proposal as it stands would 
>>>> allow currently omitted labels to continue being valid. Moreover, and I 
>>>> think this is a worthy consideration, one argument for permitting the 
>>>> omission of labels during pattern matching is to encourage API designers 
>>>> to use labels to clarify initialization without forcing its use by API 
>>>> consumers during every pattern matching operation.
>>>> 
>>>> In any case, the conclusion reached is precedented in the world of 
>>>> functions:
>>>> 
>>>> func g(a: Int, b: Int) { ... }
>>>> let f = g
>>>> f(1, 2)
>>>> 
>>>> On Sun, Sep 3, 2017 at 15:13 Robert Widmann via swift-evolution 
>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>>> Hello Swift Evolution,
>>>> 
>>>> I took up the cause of implementing SE-0155 
>>>> <https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md>,
>>>>  and am most of the way through the larger points of the proposal.  One 
>>>> thing struck me when I got to the part about normalizing the behavior of 
>>>> pattern matching 
>>>> <https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md#pattern-consistency>.
>>>>   The Core Team indicated in their rationale 
>>>> <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170417/035972.html>
>>>>  that the proposal’s suggestion that a variable binding sub in for a label 
>>>> was a little much as in this example:
>>>> 
>>>> enum Foo {
>>>>   case foo(x: Int, y: Int)
>>>> }
>>>> if case let .foo(x: x, y: y) {} // Fine!  Labels match and are in order
>>>> if case let .foo(x, y: y) {} // Bad!  Missing label 'x'
>>>> if case let .foo(x, y) {} // Fine?  Missing labels, but variable names 
>>>> match labels
>>>> 
>>>> They instead suggested the following behavior:
>>>> 
>>>> enum Foo {
>>>>   case foo(x: Int, y: Int)
>>>> }
>>>> if case let .foo(x: x, y: y) {} // Fine!  Labels match and are in order
>>>> if case let .foo(x, y: y) {} // Bad!  Missing label 'x'
>>>> if case let .foo(x, y) {} // Fine?  Missing labels, and full name of case 
>>>> is unambiguous
>>>> 
>>>> Which, for example, would reject this:
>>>> 
>>>> enum Foo {
>>>>   case foo(x: Int, y: Int) // Note: foo(x:y:)
>>>>   case foo(x: Int, z: Int) // Note: foo(x:z:)
>>>> }
>>>> if case let .foo(x, y) {} // Bad!  Are we matching foo(x:y:) or foo(x:z:)?
>>>> 
>>>> With this reasoning:
>>>> 
>>>>>  - 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.
>>>> 
>>>> 
>>>> This sounds fine in principle, but I believe it is inconsistent with the 
>>>> goals of the proposal and doesn’t actually normalize much about the 
>>>> existing pattern matching process.  As it stands, labels may be omitted 
>>>> from patterns because Swift’s philosophy before this proposal is that 
>>>> associated values in enum cases were conceptually tuples.  With the 
>>>> addition of default arguments, the ability to overload case names with 
>>>> differing associated value labels, and making the labels part of the API 
>>>> name, there is no reason we should allow tuple-like behavior in just this 
>>>> one case.
>>>> 
>>>>> While an associated-value label...
>>>> 
>>>> While it is true that a user often has a domain-specific intention for 
>>>> variables created during the destructuring process, the labels do not 
>>>> distract from the original purpose of the API and the user is still free 
>>>> to provide whatever name they see fit.
>>>> 
>>>>> 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...
>>>> 
>>>> This is phrased as a matter of choice, in practice this is perplexing.  
>>>> Recall an earlier rejected pattern:
>>>> 
>>>> enum Foo {
>>>>   case foo(x: Int, y: Int)
>>>> }
>>>> if case let .foo(x, y: y) {} // Bad!  Missing label ‘x'
>>>> 
>>>> From the user’s perspective, it is obvious what should happen: Either they 
>>>> did, or did not, intend to match labels.  From the compiler’s perspective 
>>>> this is a proper ambiguity.  Did the user intend to provide a “more 
>>>> meaningful name” and hence meant to elide the label, or did the user 
>>>> intend to match all the labels but forgot or deleted one?  It is not 
>>>> obvious why, if we’re making the distinction, we should assume one way or 
>>>> the other.   This case only gets worse when we must diagnose intent if the 
>>>> case is also overloaded by base name.
>>>> 
>>>> I don’t see how it is "unduly onerous” to teach code completion to suggest 
>>>> the full name of an enum case everywhere or to create diagnostics that 
>>>> always insert missing labels in patterns to correct the user’s mistake.  
>>>> Freedom of choice is, in this case, only making a hard problem harder.
>>>> 
>>>>> It is likely that cases will continue to be predominantly distinguished 
>>>>> by their base name alone...
>>>> 
>>>> This makes sense given the current state of the world, but under this 
>>>> proposal we fully expect users to be overloading that base name and 
>>>> writing more and more ambiguous patterns.  We should encourage 
>>>> disambiguating these cases with labels as a matter of both principle and 
>>>> QoI.  
>>>> 
>>>> A pattern is meant to mirror the way a value was constructed with 
>>>> destructuring acting as a dual to creation.  By maintaining the structure 
>>>> of the value in the pattern, labels included, users can properly convey 
>>>> that they intend the label to be a real part of the API of an enum case 
>>>> with associated values instead of just an ancillary storage area.  
>>>> Further, we can actually simplify pattern matching by making enum cases 
>>>> consistent with something function-like instead of tuple-like.
>>>> 
>>>> To that end, I'd like the rationale and the proposal to be amended to 
>>>> require labels in patterns in all cases.
>>>> 
>>>> Thoughts?
>>>> 
>>>> ~Robert Widmann
>>>> 
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>> 
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>> 
>>>> 
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> 
>>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to