On Apr 2, 2017, at 5:12 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Sun, Apr 2, 2017 at 6:30 PM, Daniel Duan <daniel@duan.org> wrote:
<snip>
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".
I'm sorry. Still don't understand :(
I have a very simple understanding of what makes something "cosmetic."
Simply, it's something that the API author can write, which the API
consumer can choose to read but is never (or, at least, rarely) required to
write.
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).
That's not true, and if we change Swift's rules to make it true, my own
code would become pervasively broken, and I suspect many others' code too.
My apologies, not sure where I got that impression.
protocol P {
func foo(_ bar: Int, _ baz: Int)
}
extension P {
func foo(_ a: Int, _ b: Int) {
print("Hello from P!")
}
}
struct S : P {
func foo(_ x: Int, _ y: Int) {
print("Hello world!")
}
}
struct T : P { }
let p: P = S()
p.foo(42, 42) // "Hello world!"
let q: P = T()
q.foo(42, 42) // "Hello from P!"
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).
Independent of the how internal names of a function are handled, I would
strongly disagree with this idea as well. Implementing a protocol is the
act of an API author--i.e., you are opting a type into a contract about its
API. Pattern matching is the act of an API user. Swift has always observed
a sharp differentiation between these two activities; it is, in essence,
the dividing line between internal and not internal. We absolutely should
not design pattern matching to parallel rules for API contracts.
I’m not satisfied by your definition of API author/user. When I conform my
type to, say, Equitable, or UITableViewDataSource, I consider myself *user*
of an API in the form of a contract: to use functionality provided by this
code, implement some stuff that meets some requirements associated with
it. The author of these protocols specify these interfaces including what
method name the user should conform with, what’s the best semantic for
them, etc.
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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution