> On Sep 8, 2017, at 2:17 PM, Robert Widmann <[email protected]> wrote:
>
>>
>> On Sep 4, 2017, at 11:35 AM, Matthew Johnson via swift-evolution
>> <[email protected] <mailto:[email protected]>> wrote:
>>
>>
>>> On Sep 4, 2017, at 11:47 AM, T.J. Usiyan <[email protected]
>>> <mailto:[email protected]>> 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 <[email protected]
>>> <mailto:[email protected]>> wrote:
>>>
>>>> On Sep 4, 2017, at 10:52 AM, T.J. Usiyan via swift-evolution
>>>> <[email protected] <mailto:[email protected]>> 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
>>>> <[email protected] <mailto:[email protected]>> 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
>>>> <[email protected] <mailto:[email protected]>> 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
>>>> [email protected] <mailto:[email protected]>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> [email protected] <mailto:[email protected]>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>
>>>>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> [email protected] <mailto:[email protected]>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>
>>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> [email protected] <mailto:[email protected]>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution