Revisiting SE-0110

The problem with this is that we also need some way to be explicit about the return type, and it would be strange to allow (and require!)
  { a, b -> Int in }
when that's not at all a valid way to write a function type. Also it would be an enormous source break, even worse than this problem.

That said, I'm quite sympathetic to the idea that directly destructuring tuples in closure parameter lists is too useful to lose, even if it's ambiguous.

John.

···

On May 25, 2017, at 2:23 PM, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:
One possibility is to make parentheses consistently meaningful in closure argument lists, so that “(a, b)” would exclusively mean “there is one parameter of tuple-type, which is being destructured”, while “a, b” without parentheses would exclusively mean “there are two parameters”.

That way you can destructure a tuple parameter if you wish (by using parentheses) or you can leave the tuple intact (by listing a single identifier without parentheses).

Let me please try to be understood:

A dictionary is a convenience type, with many many purposes. One rarely thinks of a dictionary as a "dictionary". It's more often a "cache", or a "name1 to name2 mapping", as in "name to position mapping", "lowercase string to case-preserved string mapping", or whatever you want.

The "kv", "pair", "key", "value" identifiers are not only irrelevant, they actively hinder code readability. Compare:

- ...map { $0.key ... $0.value ... }
- ...map { (name, position) in name ... position ... }

Gwendal

···

Le 27 mai 2017 à 19:43, Dave Abrahams via swift-evolution <swift-evolution@swift.org> a écrit :

on Thu May 25 2017, Gwendal Roué <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Furthermore, this probably comes up most commonly with dictionaries,
since they're a sequence of tuples. The element tuple for
dictionaries has element labels (key: Key, value: Value), so instead
of writing `{ tuple in let (key, value) = tuple; f(key, value) }`,
you could use the implicit argument and write `{ f($0.key, $0.value)
}`.

-Joe

I've migrated a project from Swift 3 to Swift 4 (relevant commit:
https://github.com/groue/GRDB.swift/commit/4f26cbcacf7b783c9c503f2909f2eb03ef7930fe\)

Joe is right, dictionaries, as handy as they are, are particularly affected. But $0 is hardly a
panacea.

What I regret the most with the change is the lost ability to give
*relevant names* to tuple elements (and sometimes with the forced
introduction of a phony variable that has no relevant name (like
"pair").

Not saying there's no problem here, but `kv` (or `keyValue` if you must)
works pretty well for this. At least it isn't purely a reflection of
the type and gives some hint as to semantics.

One possibility is to make parentheses consistently meaningful in closure argument lists, so that “(a, b)” would exclusively mean “there is one parameter of tuple-type, which is being destructured”, while “a, b” without parentheses would exclusively mean “there are two parameters”.

That way you can destructure a tuple parameter if you wish (by using parentheses) or you can leave the tuple intact (by listing a single identifier without parentheses).

The problem with this is that we also need some way to be explicit about the return type, and it would be strange to allow (and require!)
{ a, b -> Int in }
when that's not at all a valid way to write a function type

This is allowed today, but I agree it would be very strange to require it.

We also allow:
  { (a, b: Int) -> Int in } // infer ‘a' from context
and
  { (a, b: () -> Int) -> Int in } // infer ‘a' from context
but these become completely ambiguous if you remove the parens.

. Also it would be an enormous source break, even worse than this problem.

Yes.

That said, I'm quite sympathetic to the idea that directly destructuring tuples in closure parameter lists is too useful to lose, even if it's ambiguous.

I think the right thing to do here is add uniform tuple destructuring in functions, but that is clearly out of scope for 4.0.

Reverting SE-0110 is not as simple as reverting a single change. So far I have found 19 commits that addressed different aspects moving from the old behavior by searching for SE-110 and SE-0110 in commit logs. It is possible there were others that did not specifically call out the proposal.

Having said that, we are supposed to support the old behavior for "-swift-verison 3", so it may be possible to find the handful of places where the version check can be removed and old behavior restored under "-swift-verson 4" if we choose to do that as mitigation until the destructuring feature is properly designed and implemented.

Another option is attempting to do some targeted fix specifically for closure arguments but that does not seem straightforward, and does seem relatively risky.

Mark

···

On May 25, 2017, at 11:27 AM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On May 25, 2017, at 2:23 PM, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

John.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

The option of using the keyword let to disambiguate, proposed somewhere
above or on GitHub, is attractive here:

{ (a, b) -> Int in } // two parameters
{ let (a, b) -> Int in } // destructuring one parameter

{ a, let (b, c) -> Int in } // destructuring two parameters
{ let a, (b, c) -> Int in } // still destructuring two parameters
{ let (a, (b, c)) -> Int in } // destructuring one parameter
{ (a, (b, c)) -> Int in } // error: add 'let' to destructure second
parameter

···

On Thu, May 25, 2017 at 1:27 PM, John McCall via swift-evolution < swift-evolution@swift.org> wrote:

> On May 25, 2017, at 2:23 PM, Nevin Brackett-Rozinsky via swift-evolution > <swift-evolution@swift.org> wrote:
> One possibility is to make parentheses consistently meaningful in
closure argument lists, so that “(a, b)” would exclusively mean “there is
one parameter of tuple-type, which is being destructured”, while “a, b”
without parentheses would exclusively mean “there are two parameters”.
>
> That way you can destructure a tuple parameter if you wish (by using
parentheses) or you can leave the tuple intact (by listing a single
identifier without parentheses).

The problem with this is that we also need some way to be explicit about
the return type, and it would be strange to allow (and require!)
  { a, b -> Int in }
when that's not at all a valid way to write a function type. Also it
would be an enormous source break, even worse than this problem.

That said, I'm quite sympathetic to the idea that directly destructuring
tuples in closure parameter lists is too useful to lose, even if it's
ambiguous.

"attractive" might be an overstatement. :)

John.

···

On May 25, 2017, at 2:40 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Thu, May 25, 2017 at 1:27 PM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> On May 25, 2017, at 2:23 PM, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> One possibility is to make parentheses consistently meaningful in closure argument lists, so that “(a, b)” would exclusively mean “there is one parameter of tuple-type, which is being destructured”, while “a, b” without parentheses would exclusively mean “there are two parameters”.
>
> That way you can destructure a tuple parameter if you wish (by using parentheses) or you can leave the tuple intact (by listing a single identifier without parentheses).

The problem with this is that we also need some way to be explicit about the return type, and it would be strange to allow (and require!)
  { a, b -> Int in }
when that's not at all a valid way to write a function type. Also it would be an enormous source break, even worse than this problem.

That said, I'm quite sympathetic to the idea that directly destructuring tuples in closure parameter lists is too useful to lose, even if it's ambiguous.

The option of using the keyword let to disambiguate, proposed somewhere above or on GitHub, is attractive here:

{ (a, b) -> Int in } // two parameters
{ let (a, b) -> Int in } // destructuring one parameter

{ a, let (b, c) -> Int in } // destructuring two parameters
{ let a, (b, c) -> Int in } // still destructuring two parameters
{ let (a, (b, c)) -> Int in } // destructuring one parameter
{ (a, (b, c)) -> Int in } // error: add 'let' to destructure second parameter

One possibility is to make parentheses consistently meaningful in closure argument lists, so that “(a, b)” would exclusively mean “there is one parameter of tuple-type, which is being destructured”, while “a, b” without parentheses would exclusively mean “there are two parameters”.

That way you can destructure a tuple parameter if you wish (by using parentheses) or you can leave the tuple intact (by listing a single identifier without parentheses).

The problem with this is that we also need some way to be explicit about the return type, and it would be strange to allow (and require!)
{ a, b -> Int in }
when that's not at all a valid way to write a function type

This is allowed today, but I agree it would be very strange to require it.

I suppose I should have tested that. :)

We also allow:
{ (a, b: Int) -> Int in } // infer ‘a' from context
and
{ (a, b: () -> Int) -> Int in } // infer ‘a' from context
but these become completely ambiguous if you remove the parens.

Yeah, I think we need to stick with allowing the parens.

. Also it would be an enormous source break, even worse than this problem.

Yes.

That said, I'm quite sympathetic to the idea that directly destructuring tuples in closure parameter lists is too useful to lose, even if it's ambiguous.

I think the right thing to do here is add uniform tuple destructuring in functions, but that is clearly out of scope for 4.0.

The non-closure aspects of this would not be difficult. All the complexity is in deciding what to do with closures.

Reverting SE-0110 is not as simple as reverting a single change. So far I have found 19 commits that addressed different aspects moving from the old behavior by searching for SE-110 and SE-0110 in commit logs. It is possible there were others that did not specifically call out the proposal.

Having said that, we are supposed to support the old behavior for "-swift-verison 3", so it may be possible to find the handful of places where the version check can be removed and old behavior restored under "-swift-verson 4" if we choose to do that as mitigation until the destructuring feature is properly designed and implemented.

Oh, that's an interesting option.

Another option is attempting to do some targeted fix specifically for closure arguments but that does not seem straightforward, and does seem relatively risky.

John.

···

On May 25, 2017, at 3:16 PM, Mark Lacey <mark.lacey@apple.com> wrote:

On May 25, 2017, at 11:27 AM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On May 25, 2017, at 2:23 PM, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

Mark

John.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I agree with you. As I said, I'm not trying to say there's no problem
here.

···

on Sun May 28 2017, Gwendal Roué <gwendal.roue-AT-gmail.com> wrote:

Le 27 mai 2017 à 19:43, Dave Abrahams via swift-evolution <swift-evolution@swift.org> a écrit :

on Thu May 25 2017, Gwendal Roué <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> > wrote:

Furthermore, this probably comes up most commonly with dictionaries,
since they're a sequence of tuples. The element tuple for
dictionaries has element labels (key: Key, value: Value), so instead
of writing `{ tuple in let (key, value) = tuple; f(key, value) }`,
you could use the implicit argument and write `{ f($0.key, $0.value)
}`.

-Joe

I've migrated a project from Swift 3 to Swift 4 (relevant commit:
https://github.com/groue/GRDB.swift/commit/4f26cbcacf7b783c9c503f2909f2eb03ef7930fe\)

Joe is right, dictionaries, as handy as they are, are particularly affected. But $0 is hardly a
panacea.

What I regret the most with the change is the lost ability to give
*relevant names* to tuple elements (and sometimes with the forced
introduction of a phony variable that has no relevant name (like
"pair").

Not saying there's no problem here, but `kv` (or `keyValue` if you must)
works pretty well for this. At least it isn't purely a reflection of
the type and gives some hint as to semantics.

Let me please try to be understood:

A dictionary is a convenience type, with many many purposes. One
rarely thinks of a dictionary as a "dictionary". It's more often a
"cache", or a "name1 to name2 mapping", as in "name to position
mapping", "lowercase string to case-preserved string mapping", or
whatever you want.

The "kv", "pair", "key", "value" identifiers are not only irrelevant,
they actively hinder code readability. Compare:

- ...map { $0.key ... $0.value ... }
- ...map { (name, position) in name ... position ... }

--
-Dave