Revisiting SE-0110


(Tony Parker) #1

Hi everyone,

We received a pull request in swift-corelibs-foundation which is apparently in response to a language change for SE-0110.

It turns this perfectly reasonable code:

- self.forEach { (keyItem, valueItem) in

into this:

+ self.forEach { (arg) in
+ let (keyItem, valueItem) = arg

Is that really the design pattern we want to encourage? What was wrong with the previous code?

(https://github.com/apple/swift-corelibs-foundation/pull/995/files)

- Tony


(Chéyo Jiménez) #2

The way I interpreted SE-110 is that it was suppose to address anonymous arguments.

Instead of using $0.0, $0.1, One needs to use $0, $1 when there are multiple arguments.

I was not aware of any implications for explicitly named parameters.

Perhaps the issue is with the signature of forEach. Does it need to be a nested tuple?

public func forEach(_ body: ((key: Key, value: Value)) throws -> Void) rethrows

···

On May 24, 2017, at 12:12 PM, Tony Parker via swift-evolution <swift-evolution@swift.org> wrote:

Hi everyone,

We received a pull request in swift-corelibs-foundation which is apparently in response to a language change for SE-0110.

It turns this perfectly reasonable code:

- self.forEach { (keyItem, valueItem) in

into this:

+ self.forEach { (arg) in
+ let (keyItem, valueItem) = arg

Is that really the design pattern we want to encourage? What was wrong with the previous code?

(https://github.com/apple/swift-corelibs-foundation/pull/995/files)

- Tony

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


(Ben Cohen) #3

I very much agree with your concerns about this change in general.

On this specific example, though, I just wanted to point out that there doesn’t seem to be a good reason to use .forEach here.

for (key, value) in self {
  // etc
}

Would work perfectly well and is clearer IMO, still works with destructing, doesn’t have gotcha problems related to continue/break not doing what you might expect, etc.

forEach is only really a win when used on the end of a chain of map/filter-like operations, where for…in would involve bouncing back from right to left.

···

On May 24, 2017, at 12:12 PM, Tony Parker via swift-evolution <swift-evolution@swift.org> wrote:

Hi everyone,

We received a pull request in swift-corelibs-foundation which is apparently in response to a language change for SE-0110.

It turns this perfectly reasonable code:

- self.forEach { (keyItem, valueItem) in

into this:

+ self.forEach { (arg) in
+ let (keyItem, valueItem) = arg

Is that really the design pattern we want to encourage? What was wrong with the previous code?

(https://github.com/apple/swift-corelibs-foundation/pull/995/files)

- Tony

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


(Pavol Vaskovic) #4

I also ran into this issue recently with SR-4745
<https://bugs.swift.org/browse/SR-4745>. It also same up in comments on
SR-2008 <https://bugs.swift.org/browse/SR-2008>. It looks like the issue
was discussed here on SE
<http://discourse.natecook.com/t/pitch-tuple-destructuring-in-parameter-lists/1501>
exactly year ago.

Turns out Swift does not support tuple destructuring in closure arguments.
Only in let statement and for-in. What previously worked for simple cases
(not nested tuples) was some kind of side effect?!

I strongly feel we need to address this in time for Swift 4, as the
consequent implementation of SE-0110 seriously hobbles concise and readable
functional style code. I'm partial to solution suggested by Raphael Reitzig
as summarized by Ben Rimmington in comments on SR-2008
<https://bugs.swift.org/browse/SR-2008?focusedCommentId=22578&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-22578>
:

   - tuple destructuring in parameter lists (which is currently not
   supported);
   - and for closure expressions, disallowing single parentheses around
   multiple parameters, or requiring double parentheses for tuple
   destructuring.

and

One could be ambitious and propose general destructuring (similar to

Scala). By using a keyword, backwards-compatibility and unambiguity could
be achieved.

Example:

{ let (x,y,z) in ...}

When asked about what would adding support for destructuring tuples in
closure arguments entail in the compiler (could it for example reuse
implementation from for-in), Jordan Rose said:

It's 80% a new feature. The compiler would have to figure out what the

proper type of the closure is anyway (the hard part) and then assign from
that type into local variables for the destructured parameters (the easy
part).

Please do take it to swift-evolution if you're interested!

I don't think I can drive this to SE proposal at this moment. Any takers?

--Pavol

···

On Wed, May 24, 2017 at 9:12 PM, Tony Parker via swift-evolution < swift-evolution@swift.org> wrote:

Hi everyone,

We received a pull request in swift-corelibs-foundation which is
apparently in response to a language change for SE-0110.

It turns this perfectly reasonable code:

- self.forEach { (keyItem, valueItem) in

into this:

+ self.forEach { (arg) in
+ let (keyItem, valueItem) = arg

Is that really the design pattern we want to encourage? What was wrong
with the previous code?

(https://github.com/apple/swift-corelibs-foundation/pull/995/files)

- Tony

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


(Chris Lattner) #5

For consistency, the decision was to make closure parameter lists work the same way as function parameters. Function parameters do not allow destructuring of arguments in their declaration, so it seemed weird to let closures do that.

Similarly, this doesn’t compile either:

  self.ForEach(functionTakingTwoParameters)

even if the two parameters are equivalent to the element type of the self Sequence

-Chris

···

On May 24, 2017, at 12:12 PM, Tony Parker via swift-evolution <swift-evolution@swift.org> wrote:

Hi everyone,

We received a pull request in swift-corelibs-foundation which is apparently in response to a language change for SE-0110.

It turns this perfectly reasonable code:

- self.forEach { (keyItem, valueItem) in

into this:

+ self.forEach { (arg) in
+ let (keyItem, valueItem) = arg

Is that really the design pattern we want to encourage? What was wrong with the previous code?


#6

I'm no language designer, nor an compiler engineer. However we should definitely be able to use the former expression as it has better readability over the latter IMO.

···

On 2017-05-24 19:12:14 +0000, Tony Parker via swift-evolution said:

Hi everyone,

We received a pull request in swift-corelibs-foundation which is apparently in response to a language change for SE-0110.

It turns this perfectly reasonable code:

- � � � �self.forEach�{ (keyItem, valueItem)�in

into this:

+ � � � �self.forEach�{ (arg)�in
+ � � � � � �let�(keyItem, valueItem)�=�arg

Is that really the design pattern we want to encourage? What was wrong with the previous code?

(https://github.com/apple/swift-corelibs-foundation/pull/995/files)

- Tony

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

--
-Bouke


(Pavel Yaskevich) #7

Hi Tony,

The way I interpreted SE-110 is that it was suppose to address anonymous
arguments.

Instead of using $0.0, $0.1, One needs to use $0, $1 when there are
multiple arguments.

I was not aware of any implications for explicitly named parameters.

Perhaps the issue is with the signature of forEach. Does it need to be a
nested tuple?

public func forEach(_ body: ((key: Key, value: Value)) throws -> Void)
rethrows

Jose is right about this one, since the signature of forEach is a tuple
nested into paren it means that `forEach` expects a single argument
of a tuple type instead of two arguments, such "tuple argument
destructuring" was supported by Swift 3 but after SE-0110 no longer is
because type-checker is preserving top level parens in
parameters/arguments.

Best Regards, Pavel.

···

On Wed, May 24, 2017 at 12:37 PM, Jose Cheyo Jimenez via swift-evolution < swift-evolution@swift.org> wrote:

On May 24, 2017, at 12:12 PM, Tony Parker via swift-evolution < > swift-evolution@swift.org> wrote:

Hi everyone,

We received a pull request in swift-corelibs-foundation which is
apparently in response to a language change for SE-0110.

It turns this perfectly reasonable code:

- self.forEach { (keyItem, valueItem) in

into this:

+ self.forEach { (arg) in
+ let (keyItem, valueItem) = arg

Is that really the design pattern we want to encourage? What was wrong
with the previous code?

(https://github.com/apple/swift-corelibs-foundation/pull/995/files)

- Tony

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

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


(Joe Groff) #8

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

···

On May 24, 2017, at 6:18 PM, Ben Cohen via swift-evolution <swift-evolution@swift.org> wrote:

I very much agree with your concerns about this change in general.

On this specific example, though, I just wanted to point out that there doesn’t seem to be a good reason to use .forEach here.

for (key, value) in self {
  // etc
}

Would work perfectly well and is clearer IMO, still works with destructing, doesn’t have gotcha problems related to continue/break not doing what you might expect, etc.

forEach is only really a win when used on the end of a chain of map/filter-like operations, where for…in would involve bouncing back from right to left.


(TJ Usiyan) #9

I wish that we had gone in the other direction and let functions allow destructuring of parameters. That said, I guess I can see how that chance left when we decided to move away from function parameters as tuples.

TJ

···

On May 25, 2017, at 02:08, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On May 24, 2017, at 12:12 PM, Tony Parker via swift-evolution <swift-evolution@swift.org> wrote:

Hi everyone,

We received a pull request in swift-corelibs-foundation which is apparently in response to a language change for SE-0110.

It turns this perfectly reasonable code:

- self.forEach { (keyItem, valueItem) in

into this:

+ self.forEach { (arg) in
+ let (keyItem, valueItem) = arg

Is that really the design pattern we want to encourage? What was wrong with the previous code?

For consistency, the decision was to make closure parameter lists work the same way as function parameters. Function parameters do not allow destructuring of arguments in their declaration, so it seemed weird to let closures do that.

Similarly, this doesn’t compile either:

  self.ForEach(functionTakingTwoParameters)

even if the two parameters are equivalent to the element type of the self Sequence

-Chris

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


(Tony Parker) #10

Hi Tony,

The way I interpreted SE-110 is that it was suppose to address anonymous arguments.

Instead of using $0.0, $0.1, One needs to use $0, $1 when there are multiple arguments.

I was not aware of any implications for explicitly named parameters.

Perhaps the issue is with the signature of forEach. Does it need to be a nested tuple?

public func forEach(_ body: ((key: Key, value: Value)) throws -> Void) rethrows

Jose is right about this one, since the signature of forEach is a tuple nested into paren it means that `forEach` expects a single argument
of a tuple type instead of two arguments, such "tuple argument destructuring" was supported by Swift 3 but after SE-0110 no longer is
because type-checker is preserving top level parens in parameters/arguments.

Best Regards, Pavel.

Well, frankly, I don’t think we should ship with such a glaring usability regression.

What’s the mitigation plan? Perhaps we should wholesale revert it until we have time to reconsider the fallout?

- Tony

···

On May 24, 2017, at 12:51 PM, Pavel Yaskevich <pavel.yaskevich@gmail.com> wrote:
On Wed, May 24, 2017 at 12:37 PM, Jose Cheyo Jimenez via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On May 24, 2017, at 12:12 PM, Tony Parker via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi everyone,

We received a pull request in swift-corelibs-foundation which is apparently in response to a language change for SE-0110.

It turns this perfectly reasonable code:

- self.forEach { (keyItem, valueItem) in

into this:

+ self.forEach { (arg) in
+ let (keyItem, valueItem) = arg

Is that really the design pattern we want to encourage? What was wrong with the previous code?

(https://github.com/apple/swift-corelibs-foundation/pull/995/files)

- Tony

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Amit Jain) #11

In addition to dictionaries, In paradigms like Rx, this sugar helps immensely towards writing concise single-line transforms where one can quickly understand what the transform is trying to achieve. It is quite common to work with many arguments which are morphed as one moves down the chain. Adding boilerplate to every single closure in such places will be really noisy and regressive to clarity at the point of use.

···

> On May 24, 2017, at 6:18 PM, Ben Cohen via swift-evolution<swift-evolution@swift.org>wrote:
>
> I very much agree with your concerns about this change in general.
>
> On this specific example, though, I just wanted to point out that there doesn’t seem to be a good reason to use .forEach here.
>
> for (key, value) in self {
> // etc
> }
>
> Would work perfectly well and is clearer IMO, still works with destructing, doesn’t have gotcha problems related to continue/break not doing what you might expect, etc.
>
> forEach is only really a win when used on the end of a chain of map/filter-like operations, where for…in would involve bouncing back from right to left.
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


(Nate Cook) #12

Joe Groff 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) }`.

Dictionaries are definitely a common case for this, but zipped sequences and enumerated() sequences are also very common, and don't have labels attached.

Closures are so lightweight and permissive in what they accept that this change really does feel like a regression. I don't think the proposal accurately captured the impact of this change on code - that section only says "Minor changes to user code may be required if this proposal is accepted." Moreover, the examples given are all of standalone closures assigned to a constant rather than the far more prevalent inline usage in collection operations.

Chris Lattner wrote:

For consistency, the decision was to make closure parameter lists work the same way as function parameters. Function parameters do not allow destructuring of arguments in their declaration, so it seemed weird to let closures do that.

I have to say that for me, it has never seemed weird at the use site to destructure tuples like that. The only confusion I've ever seen from users is when deconstruction didn't work enough, like if the parameter was (Int, (Int, Int)) and you couldn't destructure the nested tuple.

Nate


#13

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").

Here are below four examples of regressions introduced by SE-0110:

Example 1
- return columns.index { (column, _) in column.lowercased() == lowercaseName }
+ return columns.index { $0.0.lowercased() == lowercaseName }

Example 2 :
- .map { (mappedColumn, baseColumn) -> (Int, String) in
+ .map { (pair) -> (Int, String) in
+ let mappedColumn = pair.key
+ let baseColumn = pair.value

Example 3 :
- .map { (table, columns) in "\(table)(\(columns.sorted().joined(separator: ", ")))" }
+ .map { "\($0.key)(\($0.value.sorted().joined(separator: ", ")))" }

Example 4 :
- dictionary.first { (column, value) in column.lowercased() == orderedColumn.lowercased() }
+ dictionary.first { $0.key.lowercased() == orderedColumn.lowercased() }

Gwendal Roué


(Pavel Yaskevich) #14

Hi Tony,

The way I interpreted SE-110 is that it was suppose to address anonymous
arguments.

Instead of using $0.0, $0.1, One needs to use $0, $1 when there are
multiple arguments.

I was not aware of any implications for explicitly named parameters.

Perhaps the issue is with the signature of forEach. Does it need to be a
nested tuple?

public func forEach(_ body: ((key: Key, value:
Value)) throws -> Void) rethrows

Jose is right about this one, since the signature of forEach is a tuple
nested into paren it means that `forEach` expects a single argument
of a tuple type instead of two arguments, such "tuple argument
destructuring" was supported by Swift 3 but after SE-0110 no longer is
because type-checker is preserving top level parens in
parameters/arguments.

Best Regards, Pavel.

Well, frankly, I don’t think we should ship with such a glaring usability
regression.

What’s the mitigation plan? Perhaps we should wholesale revert it until we
have time to reconsider the fallout?

There is a migrator support, and I've made a couple of diagnostic
improvements for it, that produce fix-its which are exactly
what you see in the aforementioned PR. Otherwise, I'd defer to Slava, who
was implementor of SE-0110.

···

On Wed, May 24, 2017 at 12:57 PM, Tony Parker <anthony.parker@apple.com> wrote:

On May 24, 2017, at 12:51 PM, Pavel Yaskevich <pavel.yaskevich@gmail.com> > wrote:
On Wed, May 24, 2017 at 12:37 PM, Jose Cheyo Jimenez via swift-evolution < > swift-evolution@swift.org> wrote:

- Tony

On May 24, 2017, at 12:12 PM, Tony Parker via swift-evolution < >> swift-evolution@swift.org> wrote:

Hi everyone,

We received a pull request in swift-corelibs-foundation which is
apparently in response to a language change for SE-0110.

It turns this perfectly reasonable code:

- self.forEach { (keyItem, valueItem) in

into this:

+ self.forEach { (arg) in
+ let (keyItem, valueItem) = arg

Is that really the design pattern we want to encourage? What was wrong
with the previous code?

(https://github.com/apple/swift-corelibs-foundation/pull/995/files)

- Tony

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

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


#15

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).

Nevin


#16

I even suggest that tuple destructuring gets more consideration.

That is because without restructuring, many closures that could be written as a single line have to be written with several lines (in order to explicitly destructure input tuples, as in the sample code provided by the OP):

+ self.forEach { (arg) in
+ let (keyItem, valueItem) = arg

Unfortunately, multiline closures are less nice than single line closures: multiline closures can't avoid the `return` keyword. And multiline closures have downgraded type inference:

func f<T>(_ closure: () -> T) -> T { return closure() }

// Yeah!
f { 1 }

// Meh: unable to infer complex closure return type; add explicit type to disambiguate
f {
    let x = 1
    return x
}

Of course, the type inference problem above could be fixed independently of tuple destructuring. But it looks like it is difficult to implement: see Jordan Rose's comment in https://bugs.swift.org/browse/SR-1570 which links to https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151214/002583.html.

Gwendal Roué

···

Le 25 mai 2017 à 17:47, Nate Cook via swift-evolution <swift-evolution@swift.org> a écrit :

For consistency, the decision was to make closure parameter lists work the same way as function parameters. Function parameters do not allow destructuring of arguments in their declaration, so it seemed weird to let closures do that.

I have to say that for me, it has never seemed weird at the use site to destructure tuples like that. The only confusion I've ever seen from users is when deconstruction didn't work enough, like if the parameter was (Int, (Int, Int)) and you couldn't destructure the nested tuple.


(Anders) #17

It is also fairly common not only with stdlib collection types, but also async programming libraries like RxSwift and ReactiveSwift. Given n discrete time sequence with values of type X1, X2, ... and Xn respectively, these may combine or zip the sequences into a sequence of value type `(X1, X2, …, Xn)`. Then the resulting sequence is observed with a closure `((X1, X2, …, Xn)) -> Void`, written as `{ x1, x2, …, xn in arbitraryWork() }`.

This could be a considerable regression for these libraries specifically, since Swift doesn’t seem getting variadic generics in the near future. A notable side effect would be losing return type inference on all these use cases, since the closure is now forced to be multi-line if one wants named tuple members.

···

On 25 May 2017, at 11:47 PM, Nate Cook via swift-evolution <swift-evolution@swift.org> wrote:

Joe Groff 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) }`.

Dictionaries are definitely a common case for this, but zipped sequences and enumerated() sequences are also very common, and don't have labels attached.

Closures are so lightweight and permissive in what they accept that this change really does feel like a regression. I don't think the proposal accurately captured the impact of this change on code - that section only says "Minor changes to user code may be required if this proposal is accepted." Moreover, the examples given are all of standalone closures assigned to a constant rather than the far more prevalent inline usage in collection operations.

Chris Lattner wrote:

For consistency, the decision was to make closure parameter lists work the same way as function parameters. Function parameters do not allow destructuring of arguments in their declaration, so it seemed weird to let closures do that.

I have to say that for me, it has never seemed weird at the use site to destructure tuples like that. The only confusion I've ever seen from users is when deconstruction didn't work enough, like if the parameter was (Int, (Int, Int)) and you couldn't destructure the nested tuple.

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


(Víctor Pimentel) #18

I have also migrated a couple of projects, and this appeared in every one
of them even if we didn't use any RX library. For example Alamofire has a
couple of errors due to this change. And the resulting code after applying
the Fix-It is, well, not pretty.

As you say, it will basically affect a lot of Dictionary API surface, not
only forEach but map, filter, etc.

SE-0110 seems to hinder the functional programming style, and I would
revert it because right now we know that the proposal was wrong as it
stated that the impact on existing code may be that only "minor" changes to
user code may be required:

<goog_1034749016>
https://github.com/apple/swift-evolution/blob/master/proposals/0110-distingish-single-tuple-arg.md
Impact on existing code

Minor changes to user code may be required if this proposal is accepted.
<https://github.com/apple/swift-evolution/blob/master/proposals/0110-distingish-single-tuple-arg.md#alternatives-considered>Alternatives
considered

Don't make this change.

···

On Fri, May 26, 2017 at 7:57 AM, Gwendal Roué via swift-evolution < 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/4f26cbcacf7b783c9c503f2909f2eb
03ef7930fe)

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").

Here are below four examples of regressions introduced by SE-0110:

Example 1
- return columns.index { (column, _) in column.lowercased() ==
lowercaseName }
+ return columns.index { $0.0.lowercased() == lowercaseName }

Example 2 :
- .map { (mappedColumn, baseColumn) -> (Int, String) in
+ .map { (pair) -> (Int, String) in
+ let mappedColumn = pair.key
+ let baseColumn = pair.value

Example 3 :
- .map { (table, columns) in "\(table)(\(columns.sorted().joined(separator:
", ")))" }
+ .map { "\($0.key)(\($0.value.sorted().joined(separator:
", ")))" }

Example 4 :
- dictionary.first { (column, value) in column.lowercased()
== orderedColumn.lowercased() }
+ dictionary.first { $0.key.lowercased() ==
orderedColumn.lowercased() }

Gwendal Roué

--
Víctor Pimentel


(Dave Abrahams) #19

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.

···

on Thu May 25 2017, Gwendal Roué <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").

--
-Dave


(Tony Parker) #20

Hi Tony,

The way I interpreted SE-110 is that it was suppose to address anonymous arguments.

Instead of using $0.0, $0.1, One needs to use $0, $1 when there are multiple arguments.

I was not aware of any implications for explicitly named parameters.

Perhaps the issue is with the signature of forEach. Does it need to be a nested tuple?

public func forEach(_ body: ((key: Key, value: Value)) throws -> Void) rethrows

Jose is right about this one, since the signature of forEach is a tuple nested into paren it means that `forEach` expects a single argument
of a tuple type instead of two arguments, such "tuple argument destructuring" was supported by Swift 3 but after SE-0110 no longer is
because type-checker is preserving top level parens in parameters/arguments.

Best Regards, Pavel.

Well, frankly, I don’t think we should ship with such a glaring usability regression.

What’s the mitigation plan? Perhaps we should wholesale revert it until we have time to reconsider the fallout?

There is a migrator support, and I've made a couple of diagnostic improvements for it, that produce fix-its which are exactly
what you see in the aforementioned PR. Otherwise, I'd defer to Slava, who was implementor of SE-0110.

The migrator support resulted in the fix-it below, which I believe is a subpar experience for such a core part of using collections and closures in general.

- Tony

···

On May 24, 2017, at 1:01 PM, Pavel Yaskevich <pavel.yaskevich@gmail.com> wrote:
On Wed, May 24, 2017 at 12:57 PM, Tony Parker <anthony.parker@apple.com <mailto:anthony.parker@apple.com>> wrote:

On May 24, 2017, at 12:51 PM, Pavel Yaskevich <pavel.yaskevich@gmail.com <mailto:pavel.yaskevich@gmail.com>> wrote:
On Wed, May 24, 2017 at 12:37 PM, Jose Cheyo Jimenez via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

- Tony

On May 24, 2017, at 12:12 PM, Tony Parker via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi everyone,

We received a pull request in swift-corelibs-foundation which is apparently in response to a language change for SE-0110.

It turns this perfectly reasonable code:

- self.forEach { (keyItem, valueItem) in

into this:

+ self.forEach { (arg) in
+ let (keyItem, valueItem) = arg

Is that really the design pattern we want to encourage? What was wrong with the previous code?

(https://github.com/apple/swift-corelibs-foundation/pull/995/files)

- Tony

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution