> 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