> On Mar 9, 2017, at 12:06 AM, David Hart <[email protected]> wrote:
> 
> 
>> On 9 Mar 2017, at 08:07, Daniel Duan via swift-evolution 
>> <[email protected]> wrote:
>> 
>> Thanks for the thoughtful feed Xiaodi! Replies are inline. I'm going to 
>> incorporate some of the responses into the proposal.
>> 
>>> On Mar 8, 2017, at 9:56 PM, Xiaodi Wu <[email protected]> wrote:
>>> 
>>> The rendered version differs from the text appended to your message. I'll 
>>> assume the more fully fleshed out version is what you intend to submit. 
>>> Three comments/questions:
>>> 
>>> Enum Case "Overloading"
>>> 
>>> An enum may contain cases with the same full name but with associated 
>>> values of different types. For example:
>>> 
>>> enum Expr {
>>>     case literal(Bool)
>>>     case literal(Int)
>>> }
>>> The above cases have overloaded constructors, which follow the same rules 
>>> as functions at call site for disambiguation:
>>> 
>>> // It's clear which case is being constructed in the following.
>>> let aBool: Expr = .literal(false)
>>> let anInt: Expr = .literal(42)
>>> User must specify an as expression in sub-patterns in pattern matching, in 
>>> order to match with such cases:
>>> 
>>> case .literal(let value) // this is ambiguous
>>> case .literal(let value as Bool) // matches `case literal(Bool)`
>>> 
>>> Comment/question 1: Here, why aren't you proposing to allow `case 
>>> .literal(let value: Bool)`? For one, it would seem to be more consistent.
>> 
>> The example in proposal doesn't include any labels. Are you suggesting two 
>> colons for sub-patterns with labels? Like `case .literal(value: let value: 
>> Bool)`?  This looks jarring. But I'm definitely open to other suggestions.
> 
> No, I think he was proposing replicating variable declaration syntax, where 
> the colon is used preceding the type (instead of using as):
> 
> let value: Bool = ...

We'd still need a way for user to bind a matching value to a name other than 
the label... I do realize that this would not be a problem if we drop the 
mandate that label must appear _somewhere_ in the pattern. Hmmm

>>> Second, since we still have some use cases where there's Obj-C bridging 
>>> magic with `as`, using `as` in this way may run into ambiguity issues if 
>>> (for example) you have two cases, one with associated value of type 
>>> `String` and the other of type `NSString`.
>> 
>> Either this should be rejected at declaration, or we need a way to accept a 
>> "pre-magic" resolution at pattern matching, when this scenarios is at hand. 
>> I'm on the phone so I can't verify. Wouldn't function overloading face a 
>> similar problem?
>> 
>>> Also, since enum cases are to be like functions, I assume that the more 
>>> verbose `as` version would work for free: `case .literal(let value) as 
>>> (Bool) -> Expr`?
>>> 
>> 
>> This is not being proposed. When a user sees/authors a case, their 
>> expectation for the declared case constructor should resemble that of a 
>> function. Pattern matching was considered separately since it's not 
>> relatable syntactically.
>>> Alternative Payload-less Case Declaration
>>> 
>>> In Swift 3, the following syntax is valid:
>>> 
>>> enum Tree {
>>>     case leaf() // the type of this constructor is confusing!
>>> }
>>> Tree.leaf has a very unexpected type to most Swift users: (()) -> Tree
>>> 
>>> We propose this syntax declare the "bare" case instead. So it's going to be 
>>> the equivalent of
>>> 
>>> enum Tree {
>>>     case leaf // `()` is optional and does the same thing.
>>> }
>>> 
>>> 
>>> Comment/question 2: First, if associated values are not to be modeled as 
>>> tuples, for backwards compatibility the rare uses of `case leaf()` should 
>>> be migrated to `case leaf(())`.
>>> 
>> Yes, and when user uses a arbitrary name when they should have used a label, 
>> or when labels are misspelled, the compiler should suggest the correct 
>> labels. I wasn't sure how much of migrator related thing should go into a 
>> proposal. Perhaps there should be more.
>>> Second, to be clear, you are _not_ proposing additional sugar so that a 
>>> case without an associated value be equivalent to a case that has an 
>>> associated value of type `Void`, correct? You are saying that, with your 
>>> proposal, both `case leaf()` and `case leaf` would be regarded as being of 
>>> type `() -> Tree` instead of the current `(()) -> Tree`?
>>> 
>> Correct. I'm _not_ proposing implicit `Void`.
>>> [The latter (i.e. `() -> Tree`) seems entirely fine. The former (i.e. 
>>> additional sugar for `(()) -> Tree`) seems mostly fine, except that it 
>>> would introduce an inconsistency with raw values that IMO is awkward. That 
>>> is, if I have `enum Foo { case bar }`, it would make case `bar` have 
>>> implied associated type `Void`; but, if I have `enum Foo: Int { case bar 
>>> }`, would case `bar` have raw value `0` of type `Int` as well as associated 
>>> value `()` of type `Void`?]
>>> 
>>> 
>>> Pattern Consistency
>>> 
>>> (The following enum will be used throughout code snippets in this section).
>>> 
>>> indirect enum Expr {
>>>     case variable(name: String)
>>>     case lambda(parameters: [String], body: Expr)
>>> }
>>> Compared to patterns in Swift 3, matching against enum cases will follow 
>>> stricter rules. This is a consequence of no longer relying on tuple 
>>> patterns.
>>> 
>>> When an associated value has a label, the sub-pattern must include the 
>>> label exactly as declared. There are two variants that should look familiar 
>>> to Swift 3 users. Variant 1 allows user to bind the associated value to 
>>> arbitrary name in the pattern by requiring the label:
>>> 
>>> case .variable(name: let x) // okay
>>> case .variable(x: let x) // compile error; there's no label `x`
>>> case .lambda(parameters: let params, body: let body) // Okay
>>> case .lambda(params: let params, body: let body) // error: 1st label 
>>> mismatches
>>> User may choose not to use binding names that differ from labels. In this 
>>> variant, the corresponding value will bind to the label, resulting in this 
>>> shorter form:
>>> 
>>> case .variable(let name) // okay, because the name is the same as the label
>>> case .lambda(let parameters, let body) // this is okay too, same reason.
>>> case .variable(let x) // compiler error. label must appear one way or 
>>> another.
>>> case .lambda(let params, let body) // compiler error, same reason as above.
>>> Comment/question 3: Being a source-breaking change, that requires extreme 
>>> justification, and I just don't think there is one for this rule. The 
>>> perceived problem being addressed (that one might try to bind `parameters` 
>>> to `body` and `body` to `parameters`) is unchanged whether enum cases are 
>>> modeled as tuples or functions, so aligning enum cases to functions is not 
>>> in and of itself justification to revisit the issue of whether to try to 
>>> prohibit this. 
>>> 
>> To reiterate, here patterns are changed not for any kind of "alignment" with 
>> function syntax. It changed because we dropped the tuple pattern (which 
>> remains available for matching with tuple values, btw), therefore we need to 
>> consider what a first-class syntax for enum case would look like.
>> 
>> The justification for this breaking change is this: with tuples, labels in 
>> pattern is not well enforced. User can skip them, bind value to totally 
>> arbitrary names, etc. I personally think emulating such rule prevents us 
>> from making pattern matching easier to read for experienced devs and easier 
>> to learn for new comers. 
>> 
>> It's reasonable to expect existing patterns in the wild either already use 
>> the labels found in declaration or they are matching against label-less 
>> cases. In other words, existing code with good style won't be affected much. 
>> For the code that actually would break, I think the migrator and the 
>> compiler can provide sufficient help in form of migration/fixits/warnings.
>> 
>> Ultimately I think requiring appearance of labels one way or another in 
>> patterns will improve both the readability of the pattern matching site as 
>> well as forcing the author of case declaration consider the use site more.
>>> In fact, I think the proposed solution suffers from two great weaknesses. 
>>> First, it seems ad-hoc. Consider this: if enum cases are to be modeled as 
>>> functions, then I should be able to write something intermediate between 
>>> the options above; namely: `case .variable(name:)(let x)`. Since 
>>> `.variable` unambiguously refers to `.variable(name:)`, I should also be 
>>> allowed to write `.variable(let x)` just as I am now.
>>> 
>> Again, patterns are not to be modeled after functions. Only the declaration 
>> and usage of case constructors are.
>>> Second, it seems unduly restrictive. If, in the containing scope, I have a 
>>> variable named `body` that I don't want to shadow, this rule would force me 
>>> to either write the more verbose form or deal with shadowing `body`. If a 
>>> person opts for the shorter form, they are choosing not to use the label.
>>> 
>> In fact this (to avoid label conflict in nesting) is the only reason the 
>> longer form allows rebinding to other names at all! You say "unduly 
>> restrictive", I say "necessarily flexible" :)
>> 
>>> 
>>> Only one of these variants may appear in a single pattern. Swift compiler 
>>> will raise a compile error for mixed usage.
>>> 
>>> case .lambda(parameters: let params, let body) // error, can not mix the 
>>> two.
>>> Some patterns will no longer match enum cases. For example, all associated 
>>> values can bind as a tuple in Swift 3, this will no longer work after this 
>>> proposal:
>>> 
>>> // deprecated: matching all associated values as a tuple
>>> if case let .lambda(f) = anLambdaExpr {
>>>     evaluateLambda(parameters: f.parameters, body: f.body)
>>> }
>>> 
>>> 
>> _______________________________________________
>> 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

Reply via email to