> 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