Daniel Duan Sent from my iPhone
> On Apr 1, 2017, at 11:49 PM, Xiaodi Wu <[email protected]> wrote: > >> On Sun, Apr 2, 2017 at 1:03 AM, Daniel Duan <[email protected]> wrote: >> >>> On Apr 1, 2017, at 2:54 PM, Xiaodi Wu via swift-evolution >>> <[email protected]> wrote: >>> >>>> On Sat, Apr 1, 2017 at 3:38 PM, Daniel Duan <[email protected]> wrote: >>>> Thanks again for a detailed review. I have a few comments inline. >>>> >>>>>> On Apr 1, 2017, at 9:50 AM, Xiaodi Wu via swift-evolution >>>>>> <[email protected]> wrote: >>>>>> >>>>>> • Does this proposal fit well with the feel and direction of Swift? >>>>> >>>>> The "Pattern consistency" section does not align well with the feel and >>>>> direction of Swift. Specifically, it does not explore some of the >>>>> difficulties that arise from the proposed rules, adopts some of the same >>>>> shortcomings that required revision for SE-0111, and deviates from some >>>>> of the anticipated fixes for those shortcomings outlined in the core >>>>> team's "update and commentary" to SE-0111. >>>>> >>>>> It is not the case that the design proposed is "a consequence of no >>>>> longer relying on tuple patterns," in that it is not the inevitable >>>>> result that falls out of that decision. >>>> >>>> The text in this revision may be poorly phrased. The connection, as I >>>> pointed out in an previous thread, is that we need to define syntax for >>>> enum pattern matching because the one we’ve been using in Swift 3 is tuple >>>> pattern’s syntax, which is now distinct and separate. >>> >>> What I'm saying here is that, although _some_ change becomes necessary, the >>> particular changes proposed here are not themselves "a consequence of no >>> longer relying on tuple patterns." >>> >>> Put another way, given `enum E { case foo(bar: Int, baz: Int) }`, not being >>> allowed to write `switch e { case foo(let a, let b): break }` is *not* an >>> inevitable consequence of moving away from tuple patterns. Since the >>> particular proposed changes break more existing source code than is >>> strictly necessary for moving away from tuple-based pattern matching, those >>> choices require stringent justification. >>> >>>>> I will detail the alternative design that requires the fewest deviations >>>>> or special rules, and breaks the least code extant today, later on. >>>>> First, the shortcomings: >>>>> >>>>> 1. >>>>> The proposed rules for pattern matching are a source-breaking change, and >>>>> are *not* the most minimal such change given the abandoning of tuples >>>>> (see alternative below). However, the proposal does not engage with the >>>>> core team's Swift 4 criteria for source-breaking changes with respect to >>>>> the proposed "stricter rules" for pattern matching. There is no text at >>>>> all about why specifically having the compiler encourage local _variable_ >>>>> names to match argument labels resolves an active harm that outweighs the >>>>> goal of preserving the greatest possible source compatibility. >>>> >>>> With this proposal, user can still use local variable names. It is true >>>> that if there are many ways to achieve the same thing, the compiler would >>>> be encouraging user to do that thing. But that puts a cost on the >>>> compiler, new users and experienced readers in unfamiliar codebases. This >>>> is (albeit not to a satisfactory degree, it seems) pointed out in the >>>> motivation section. >>>> >>>> As for source compatibility, Swift 3 code should continue to work with >>>> warnings. Swift 4 mode would issue errors along with fix-its, which the >>>> migrator can leverage. Depends on core team/community’s implementor >>>> resource, there’s even a chance that this change would roll out one >>>> version later (warning in 4.X, error in 5.Y). In theory, the migration >>>> hurdle can be minimized. >>> >>> Many syntactic changes can be migrated in this way, but for Swift 4, that >>> would only be justified when the existing syntax meets a high bar for being >>> harmful. Again, the overarching theme of my response is that I don't think >>> the proposed "stricter rules" offer much more harm mitigation than >>> significantly less source-breaking designs for pattern matching, and I >>> don't see anything in the proposal text that discusses the issue or >>> justifies the particular design over less source-breaking alternatives. >>> >>>>> >>>>> OTOH, the proposal does outline a major use case for a local variable >>>>> name that does not match the argument label: `param` vs `parameter`. >>>>> Widely-respected style guides in various languages encourage >>>>> unabbreviated and descriptive API names but much more concise local >>>>> variable names. This is a legitimate and good practice being actively >>>>> discouraged by the sugared rules. >>>> >>>> This not a counterpoint, but I personally think using shortened names is >>>> not something to be encouraged. A (admittedly quirky) practice some of us >>>> inherited from the Cocoa style guideline is to use real, complete words >>>> for variable names. I’d like to think that The Swift API Design Guidelines >>>> are aligned in spirit on this matter - “clarity is more important than >>>> brevity”. (incidentally, the guidelines’s code samples don’t contain >>>> partial-word variables anywhere). >>> >>> We're talking _local_ variables: local variables aren't API. There are >>> many, many examples of single-letter variables in the design guidelines. >>> For example, `x = y.union(z)` has three of them. >>> >>>> >>>>> >>>>> This would be merely annoying and not harmful if we could guarantee that >>>>> it only means the API user will have to use longer local names, but the >>>>> natural impulse on the part of thoughtful API authors would be to limit >>>>> the expressiveness of their labels to help out their users. >>>>> >>>>> This puts API authors in an impossible bind: they need to choose labels >>>>> that are not too short lest it collide frequently with existing local >>>>> variable names (`x` and `y` would be suboptimal, for example, but there >>>>> are good reasons why an associated value might have arguments labeled `x` >>>>> and `y`), >>>> >>>> API authors are already in this impossible bind: whenever they export a >>>> type name, a method signature in an open class or a protocol, risk of >>>> collision come up. >>> >>> Again, local variables aren't API. API authors have never been in this bind >>> with respect to local variables. Nothing in the language has ever caused >>> API to restrict the consumer's choice of local variable names. I think this >>> is a highly, highly unusual rule. >>> >> >> Local variable being the same as argument label, which is API, correct? I’m >> saying user’s local variables and types can collide with symbols from APIs. >> To illustrate, imagine implementing a protocol (yes, as simple as that): >> >> protocol A { >> var answer: Int { get } >> func ask(_ question: String) >> } >> >> if blah() { >> let answer = 0 >> let question = "huh?" >> >> class B: A { >> let answer = 42 >> func ask(_ question: String) { >> // what's question and answer here? >> // what if you want to define a new type here with the name “A”? >> } >> } >> } >> >> `A` forced user to shadow their local variable (a collision!), so it’s wise >> for the user to pick some other variable name here. Why does seem so trivial >> and natural? Because it’s how API works: someone defines some symbol, you >> take them into your local scope and use them. The pattern matching rule >> proposed here is no different. >> >>>> When a local variable does collide with a payload label, it would be bad >>>> if the user accidentally used the variable _in stead of_ the actual >>>> payload value. Forcing users to proactively rebind the variable would make >>>> them more mindful for this type of mistake. >>> >>> What mistake do you have in mind? Currently, labels have nothing to do with >>> variable names. How does a user accidentally use a label name instead of a >>> variable name? >> >> Looking at definition of an enum, user sees something like >> >> enum SomeEnum { >> case aCase(veryMundaneName: Type) // substitute “veryMundaneName” with a >> common label, like “value” or “account" >> } >> >> What’s the value of `veryMundaneName` in a pattern matched black for >> `aCase`? The answer in Swift 3 is: no one knows! User may use this variable >> expecting it’s bind to the associated value because it’s natural given the >> context, and later find out that they’ve been using a variable from outside >> because the associated value is bond to something completely unrelated. >> >> >> Example: >> >> switch enumValue { >> case aCase….: >> // many lines of code later… >> doThings(with: veryMundaneName) // bug! >> } >> >> Turns out, the bug is due to >> >> let veryMundaneName: AType = getAMundaneValue() >> // many lines of code later >> switch enumValue { >> case aCase(let randomLabelFreedomYay): >> // many lines of code later >> doThings(with: veryMundaneName) // bug! >> } >> >> This mistake seems silly, and is still a problem in the case of rebinding. >> But we can make it happen less. > > I am not convinced this is an illustration of a bug related to enum cases in > any real sense. You are invoking a function with one variable when you meant > to invoke it with another. This can happen with any two variables in any > scenario. I see no evidence that argument labels are any more prone to be > confused for variable names than are case names, function names, or any other > name. It is that Swift is strongly typed that makes confusion happen less, > given that `veryMundaneName` is of type `AType` and `randomLabelFreedomYay` > is of type `Type`. > >>>> >>>>> but they also need to choose labels that are not too verbose. The safest >>>>> bet in this case would be not to label at all, but then they lose the >>>>> communicative aspect of argument labels (see point 2 below). >>>> >>>> A more realistic version of the story: API author choose labels that make >>>> the most sense for the declaration and user accept the risk of collision >>>> as they use the API. Most of those who choose to skip labels would not >>>> have given this much thought about their effect at all. >>>> >>>>> >>>>> 2. >>>>> In the "update and commentary" revising SE-0111, it was acknowledged that >>>>> "cosmetic" labels have a significant use case. Thus, the rules were >>>>> changed to allow `(_ foo: Int, _ bar: Int) -> ()` to communicate to the >>>>> reader of code that the first argument serves some purpose "foo" without >>>>> forcing that name to be part of the API, pending further revisions. >>>>> >>>>> Because enum cases are currently tuples, labels can be dropped freely, >>>>> and therefore these labels are effectively "optional" parts of the API >>>>> that can be seen by the user but, at their discretion, not used. That >>>>> fulfills the use case of "cosmetic" labels. In this revised proposal, by >>>>> requiring the argument label to be actually _written_ somewhere by the >>>>> API user, it puts a dent into the legitimate use case of "cosmetic" >>>>> labels. >>>>> >>>>> That is to say, an API author who wishes to communicate something about a >>>>> parameter by using a label must now also consider if that label is also >>>>> appropriate as a variable name and must forgo its use if the label is not >>>>> so appropriate. This is a very different decision-making process and it >>>>> is being applied retroactively to previously designed APIs whose labels >>>>> would have been (hopefully thoughtfully) chosen under very different >>>>> circumstances. >>>> >>>> This is something we never agreed on: SE-0111 is about functions. In some >>>> languages, patterns does resemble constructor functions, but that’s as >>>> much similarity as one can get anywhere. I still think applying every >>>> decision we made about functions to pattern matching is weird. >>> >>> I have to admit, I still don't understand your reticence. The first part of >>> your proposal aligns enum cases with functions. If we are to look for >>> patterns in something that is spelled like a function, then it is natural >>> for the pattern itself to be spelled like a function, no? Currently, in >>> Swift 3, since we're trying to use pattern matching for a tuple, the >>> pattern is spelled like a tuple. In my simplistic mind, if we're trying to >>> use pattern matching for a $foo, the pattern should be spelled like a $foo. >>> Far from being weird, to me that is the only possible intuitive syntax. >>> >>>> But here’s my analysis anyways: the “cosmetic label” comment is about >>>> paving a way to restore expressivity of closures. It talks about the >>>> *interaction* between a function/closure’s declaration and use site — if >>>> parameter names are provided in a closure’s declaration, they should be >>>> required at invocation, similar to pre-SE-0111. IMO this proposal makes >>>> enum case and patterns closer to this goal. >>> >>> I agree that your proposal does indeed get us closer to SE-0111. By >>> requiring argument labels chosen by the API author to be written out by the >>> user, we get closer to the goals of SE-0111. But SE-0111 also had a large >>> drawback that required post-approval modification, which was that there >>> ended up being no way to write "cosmetic labels," which both the community >>> and core team agreed was an important use case. >>> >>> With functions, that role can be filled with internal parameter names. This >>> is what the "update and commentary" restored to SE-0111. With tuples, that >>> role is filled by the labels themselves, because they can be ergonomically >>> erased. With enum cases, you have not provided a parallel facility for >>> cosmetic labels, because in your proposal labels can no longer be easily >>> erased, but nor are there internal parameter names or some other >>> substitute. I'm saying that we should learn from the problems discovered >>> after SE-0111 was approved and fix that shortcoming for enum cases before >>> this proposal is adopted. >> >> (Link to what we are talking about for the benefit of those reading along: >> https://lists.swift.org/pipermail/swift-evolution-announce/2016-July/000233.html) >> >> The key distinction we need to decide here is whether case labels are >> “cosmetic”. We don’t allow declaration of separate parameter name and >> internal name for associated values. I interpret that as we are enforcing >> the syntax sugar in function declaration where user can use one symbol to >> represent both: >> >> func f(x: Int) // is the same as func f(x x: Int) >> >> It’s tempting to treat matching an enum value against a pattern as assigning >> a function value to a variable. > > Sorry, I am not sure I understand this sentence. Aka viewing the case pattern as simply an compound variable assignment as envisioned in the SE-0111 commentary. This way of the labels would be "cosmetic". > >> If that’s what we are doing, it makes perfect sense to say we get “ultimate >> glory” here with patterns. Meaning, as you suggested, we consider the case >> labels “cosmetic”. It’s really just tho parameter name in a function (the >> first of the two “x” in code comment above. >> >> But that’s kind of a stretch isn’t it? An enum value is very different >> compared to a function value. Yes, there happen to be a function that >> constructs this enum value that’s declared when user declare a case, that >> function gets as much resemblance as any other. But the enum value it self >> deserves more consideration. Telling the user “do these things that you do >> with a function value” just makes pattern matching harder to explain, >> because we are *not* assigning nor invoking function values. > > Ah, I see. You think of the associated value as something distinct from the > declaration used to initialize it. However, there is no spelling for an > associated value other than what is used to initialize it. Given `case > foo(bar: Int, baz: Int, boo: Int)`, previously, the full name of the case was > `foo` and the associated value was `(bar: Int, baz: Int, boo: Int)`. Your > proposal causes the full name of the case instead to be `foo(bar:baz:boo:)` > and the associated value to be `(Int, Int, Int)`. Is that not your > understanding of it? Yes > Pattern matching is just a matter of (a) indicating what case you want to > identify with the pattern; and (b) what parts of the associated value you > wish to match or to bind to variables. Part (a) is done by writing the name, > either the base name or in full (i.e. either `foo` or `foo(bar:baz:boo:)`). > Part (b) is done by writing `let myVariableName` in the intended positions. What I left out is that the internal/parameter names of a function are non-optional part of its signature (one must use exact parameter names to implement a method in a protocol, for example). I prefer treating labels in case pattern matching the same way we treat parameter names in protocol method implementation (due to the symmetry between constructing/deconstructing body mentioned in my previous comments). >> That’s not to say we need totally distinct syntax. Deconstructing a value >> should visually relate to constructing it. So here’s how I think these two >> relate: a constructor is a function. Function signature has these arguments >> that the function refers to in its body. Pattern matching is the starting >> point of deconstructing a value. The scope created following it is the >> equivalent of a “body”, in which the associated values are used as >> “arguments”. Therefore it make sense to say that these labels are more like >> internal names (the 2nd “x” in the comment of the above sample). >> >>>>> 3. >>>>> The first part of the proposal aligns enum case syntax with functions. >>>>> Functions often taken prepositions as argument labels, and indeed >>>>> previous SE proposals have extended the rules to allow most words. >>>>> However, `case foo(index: Int, in: T)` would have a disastrous label, as >>>>> `in` would be a very annoying variable name whose use would be actively >>>>> encouraged by the proposed sugared pattern matching rules. >>>>> >>>>> The proposed rules for the sugared pattern would also require (well, >>>>> greatly encourage) unique labels for each argument. This again is >>>>> inconsistent with the naming conventions encouraged by the first part of >>>>> the proposal aligning enum case syntax with functions, which have no such >>>>> restrictions. If a user names something `case foo(point: T, point: T)`, >>>>> then the matching rules would actively encourage an invalid redefinition >>>>> of a variable named `point`. >>>>> >>>>> (On the other hand, the API author does not have the luxury of naming the >>>>> same case `foo(from point: T, to point: T)`, and even if they did, >>>>> prepositions can make lousy local variable names--see first paragraph.) >>>> >>>> I don’t see this as a problem for enum case authors. It just means the >>>> poor pattern writer needs to provide the positional information to >>>> disambiguate. >>> >>> What do you mean by "positional information" here? >>> >>>>> 4. >>>>> The proposal does not explore what happens when the proposed prohibition >>>>> on "mixing and matching" the proposed sugared and unsugared pattern >>>>> matching runs up against associated values that have a mix of labeled and >>>>> unlabeled parameters, and pattern matching user cases where the user does >>>>> not wish to bind all of the arguments. >>>>> >>>>> Given `case foo(a: Int, String, b: Int, String)`, the only sensible >>>>> interpretation of the rules for sugared syntax would allow the user to >>>>> choose any name for some but not all of the labels. If the user wishes to >>>>> bind only `b`, however, he or she will need to navigate a puzzling set of >>>>> rules that are not spelled out in the proposal: >>>>> >>>>> ``` >>>>> case foo(a: _, _, b: let b, _) >>>>> // this is definitely allowed >>>>> >>>>> case foo(a: _, _, b: let myVar, _) >>>>> // this is also definitely allowed >>>>> >>>>> // but... >>>>> case foo(_, _, b: let myVar, _) >>>>> // is this allowed, or must the user explicitly state and not bind `a`? >>>>> >>>>> // ...and with respect to the sugared version... >>>>> case foo(_, _, let b, _) >>>>> // is this allowed, or must the user explicitly state and not bind `a`? >>>>> ``` >>>>> >>>> >>>> Good point. To make up for this: `_` can substitute any sub pattern, which >>>> is something that this proposal doesn’t change but definitely worth >>>> spelling out. >>>> >>>>> 5. >>>>> In the "update and commentary" revising SE-0111, the core team outlined a >>>>> preferred path to restoring the full use of argument labels for functions >>>>> without giving them type system significance. They gave a non-sugared >>>>> form and a sugared form, both of which have met with approval from the >>>>> community. >>>>> >>>>> Briefly, the non-sugared form allows compound names to be used in >>>>> variable names: `func foo(opToUse op(lhs:rhs:) : (Int, Int) -> Int)`. The >>>>> first part of this proposal is consistent in that it removes the type >>>>> system significance of argument labels from the associated values of enum >>>>> cases, and considers them as part of the enum case name. It also stands >>>>> to reason that, if a user were to match a case _without_ trying to bind >>>>> any variables, the same syntax would have be used if the base name is >>>>> ambiguous: `case elet(locals:body:): break`. >>>>> >>>>> However, the proposal makes no provision for using that same compound >>>>> name in pattern matching. There appears to be no particular reason for >>>>> its isolated omission here, as `case elet(locals:body:)(let a, let b): >>>>> return a * b` is readable and presents no syntactic difficulties. >>>>> (Moreover, it is consistent with the syntax permitted in this proposal >>>>> for initializing a variable: `let foo = Expr.elet(locals:body:)([], >>>>> anExpr)`.) >>>> >>>> Another good point. We can handle this in the purely additional proposal >>>> for compound variable names. I consider this not the 5th item in the list, >>>> but a separate suggestion, however :P >>>> >>>>> >>>>> --- >>>>> >>>>> In light of these shortcomings, I would argue that the following >>>>> alternative scheme is the most intuitive and consistent for pattern >>>>> matching given the general agreement that enum case representation should >>>>> be "normalized": >>>>> >>>>> Given: >>>>> >>>>> ``` >>>>> enum S { >>>>> case foo(bar: Int, baz: Int) >>>>> case foo(boo: String) >>>>> case bar(boo: String) >>>>> } >>>>> ``` >>>>> >>>>> a. As in functions after SE-0111, enum cases can be identified >>>>> unambiguously, regardless of whether one is initializing a variable or >>>>> matching a case, by their compound name, e.g. `bar(boo:)`. Where a case >>>>> can be unambiguously identified with only the base name, that is an >>>>> alternative spelling, e.g. `bar`. Where a case cannot be identified >>>>> uniquely with the base name, then it is an error to try to use the base >>>>> name alone: `case foo: break // error: unambiguous`. >>>>> >>>>> b. As in functions after SE-0111, arguments can be passed in either a >>>>> sugared form or an unsugared form, and they can be bound in a pattern >>>>> matching statement in the same way. That is, `case foo(bar: let a, baz: >>>>> let b): break` and `case foo(bar:baz:)(let a, let b): break` are >>>>> equivalent. >>>>> >>>>> c. As in functions, one cannot supply different or incorrect argument >>>>> labels. That is, `case foo(baz: let a, bar: let b)` and `case >>>>> foo(baz:bar:)(let a, let b)` are both forbidden. _This recovers the vast >>>>> majority of the additional syntactic safety that is outlined in the >>>>> revised proposal, but without the use of any special rules for pattern >>>>> matching._ >>>>> >>>>> d. By composing rules (a) and (b), `case bar(let a)` is allowed as it is >>>>> today, preserving source compatibility. However `case foo(let b, let c)` >>>>> is not allowed, and _not_ because different local variable names are >>>>> chosen, but because the enum has two cases named foo. >>>> >>>> From a user’s point of view, there’s enough positional information in this >>>> pattern for the compiler to figure out which case it should match. This >>>> would be very unintuitive IMO. >>> >>> Wait, the key point of your proposal, with its "stricter rules," is that >>> labels shouldn't be optional even with sufficient positional information! >>> That's also the whole thing above about getting us closer to aligning with >>> SE-0111, etc. >> >> Fair enough. The argument I invoked leads us to a dark path :P >> >> >>> _______________________________________________ >>> swift-evolution mailing list >>> [email protected] >>> https://lists.swift.org/mailman/listinfo/swift-evolution >> >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
