Revisiting SE-0110

Yes, I agree. We need to add back tuple destructuring in closure parameter
lists because this is a serious usability regression. If we're reluctant to
just "do the right thing" to handle the ambiguity of (a,b), we should at least
allow it via unambiguous syntax like ((a,b)). I do think that we should just
"do the right thing", however, with my biggest concern being whether there's
any reasonable way to achieve that in 4.0.

Closure parameter lists are unfortunately only half of the equation here. This
change also regresses the usability of point-free expression.

The consequences for point-free style were expected and cannot really be
eliminated without substantially weakening SE-0110. Closure convenience seems to
me to be a much more serious regression.

John, do you also want to say "and without weakening SE-0066"? Because, if I understand correctly, in this case:

No. SE-0066 was ultimately just a syntax proposal. SE-0110 clarified the intended type-system behavior of function types for both calls and conversions. Removing tuple destructuring from parameter clauses was an incidental consequence of that, which in my opinion was pursued out of enthusiasm and without adequately understanding the impact on closures.

I understand that there are developers who dislike SE-0110's impact on certain kinds of functional programming, but that is a very broad complaint that is unlikely to reach consensus or acceptance, especially for Swift 4. In contrast, I think we may be able to gain consensus on a more targeted proposal that just re-admits tuple destructuring in closures, assuming we can find an acceptable implementation.

John.

···

On Jun 1, 2017, at 3:25 AM, Vladimir.S <svabox@gmail.com> wrote:
On 01.06.2017 0:42, John McCall wrote:

On May 31, 2017, at 2:02 PM, Stephen Celis <stephen.celis@gmail.com> wrote:

On May 28, 2017, at 7:04 PM, John McCall via swift-evolution >>>> <swift-evolution@swift.org> wrote:

func add(_ x: Int, _ y: Int) -> Int {
   return x + y
}

zip([1, 2, 3], [4, 5, 6]).map(add)

.. we have a clear function type mismatch situation, when map() expects function of type ((Int, Int))->Int, but function of type (Int,Int)->Int is provided ? So probably the additional 'reason' of the 'problem' in this case is SE-0066, no?
Or I don't understand the SE-0066 correctly..
Do we want to allow implicit conversions between function type ((Int,Int))->Int and (Int,Int)->Int?

Quote from SE-0066:
---
(Int, Int) -> Int // function from Int and Int to Int
((Int, Int)) -> Int // function from tuple (Int, Int) to Int
---

During this discussion I see a wish of some group of developers to just return back tuple splatting for function/closure arguments, so they can freely send tuple to function/closure accepting a list of parameters(and probably vise-versa).
Is it worth to follow SE-0066 and SE-0110 as is, i.e. disallow tuple deconstructing and then, as additive change improve the situation with tuple splatting/deconstructing later with separate big proposal?

Btw, about the SE-0110 proposal. It was discussed, formally reviewed and accepted. I expect that its revision also should be formally proposed/reviewed/accepted to collect a wide range of opinions and thoughts, and attract the attention of developers in this list to the subject.

Also, if we revisit SE-0110, will this code be allowed?:

func foo(_ callback: ((Int,Int))->Void) {}
let mycallback = {(x:Int, y:Int)->Void in }
foo(mycallback)

and

func foo(_ callback: (Int,Int)->Void) {}
let mycallback = {(x: (Int, Int))->Void in }
foo(mycallback)

If so, what will be result of this for both cases? :

print(type(of:mycallback)) // (Int,Int)->Void or ((Int,Int))->Void

If allowed, do we want to allow implicit conversion between types (Int,Int)->Void and ((Int,Int))->Void in both directions? (Hello tuple splatting?)

John.

func add(_ x: Int, _ y: Int) -> Int { return x + y }
zip([1, 2, 3], [4, 5, 6]).map(add)
// error: nested tuple parameter '(Int, Int)' of function '(((_.Element,
_.Element)) throws -> _) throws -> [_]' does not support destructuring
This may not be a common pattern in most projects, but we heavily use this style
in the Kickstarter app in our functional and FRP code. Definitely not the most
common coding pattern, but a very expressive one that we rely on.
Our interim solution is a bunch of overloaded helpers, e.g.:
func tupleUp<A, B, C>(_ f: (A, B) -> C) -> ((A, B)) -> C { return }
zip([1, 2, 3], [4, 5, 6]).map(tupleUp(add))
Stephen

.

Dear all,
I made a comparison of Swift's 4 lack of tuple unsplatting, here is how it stands in comparison with other languages
https://gist.github.com/blender/53f9568617654c38a219dd4a8353d935

Yes, I agree. We need to add back tuple destructuring in closure parameter
lists because this is a serious usability regression. If we're reluctant to
just "do the right thing" to handle the ambiguity of (a,b), we should at least
allow it via unambiguous syntax like ((a,b)). I do think that we should just
"do the right thing", however, with my biggest concern being whether there's
any reasonable way to achieve that in 4.0.

Closure parameter lists are unfortunately only half of the equation here. This
change also regresses the usability of point-free expression.

The consequences for point-free style were expected and cannot really be
eliminated without substantially weakening SE-0110. Closure convenience seems to
me to be a much more serious regression.

John, do you also want to say "and without weakening SE-0066"? Because, if I
understand correctly, in this case:

func add(_ x: Int, _ y: Int) -> Int {
return x + y
}

zip([1, 2, 3], [4, 5, 6]).map(add)

.. we have a clear function type mismatch situation, when map() expects function of
type ((Int, Int))->Int, but function of type (Int,Int)->Int is provided ? So probably
the additional 'reason' of the 'problem' in this case is SE-0066, no?
Or I don't understand the SE-0066 correctly..
Do we want to allow implicit conversions between function type ((Int,Int))->Int and
(Int,Int)->Int?

Quote from SE-0066:

···

On Thursday, June 1, 2017 12:25 PM, Vladimir.S via swift-evolution <swift-evolution@swift.org> wrote:
On 01.06.2017 0:42, John McCall wrote:

On May 31, 2017, at 2:02 PM, Stephen Celis <stephen.celis@gmail.com> wrote:

On May 28, 2017, at 7:04 PM, John McCall via swift-evolution >>> <swift-evolution@swift.org> wrote:

---
(Int, Int) -> Int // function from Int and Int to Int
((Int, Int)) -> Int // function from tuple (Int, Int) to Int
---

During this discussion I see a wish of some group of developers to just return back
tuple splatting for function/closure arguments, so they can freely send tuple to
function/closure accepting a list of parameters(and probably vise-versa).
Is it worth to follow SE-0066 and SE-0110 as is, i.e. disallow tuple deconstructing
and then, as additive change improve the situation with tuple
splatting/deconstructing later with separate big proposal?

Btw, about the SE-0110 proposal. It was discussed, formally reviewed and accepted. I
expect that its revision also should be formally proposed/reviewed/accepted to
collect a wide range of opinions and thoughts, and attract the attention of
developers in this list to the subject.

Also, if we revisit SE-0110, will this code be allowed?:

func foo(_ callback: ((Int,Int))->Void) {}
let mycallback = {(x:Int, y:Int)->Void in }
foo(mycallback)

and

func foo(_ callback: (Int,Int)->Void) {}
let mycallback = {(x: (Int, Int))->Void in }
foo(mycallback)

If so, what will be result of this for both cases? :

print(type(of:mycallback)) // (Int,Int)->Void or ((Int,Int))->Void

If allowed, do we want to allow implicit conversion between types (Int,Int)->Void and
((Int,Int))->Void in both directions? (Hello tuple splatting?)

John.

func add(_ x: Int, _ y: Int) -> Int { return x + y }

zip([1, 2, 3], [4, 5, 6]).map(add)

// error: nested tuple parameter '(Int, Int)' of function '(((_.Element,
_.Element)) throws -> _) throws -> [_]' does not support destructuring

This may not be a common pattern in most projects, but we heavily use this style
in the Kickstarter app in our functional and FRP code. Definitely not the most
common coding pattern, but a very expressive one that we rely on.

Our interim solution is a bunch of overloaded helpers, e.g.:

func tupleUp<A, B, C>(_ f: (A, B) -> C) -> ((A, B)) -> C { return }

zip([1, 2, 3], [4, 5, 6]).map(tupleUp(add))

Stephen

.

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

Hmm. Could you please comment this(below) part of SE-0066? :

···

On 01.06.2017 21:52, John McCall wrote:

On Jun 1, 2017, at 3:25 AM, Vladimir.S <svabox@gmail.com> wrote:

On 01.06.2017 0:42, John McCall wrote:

On May 31, 2017, at 2:02 PM, Stephen Celis <stephen.celis@gmail.com> wrote:

On May 28, 2017, at 7:04 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote: Yes, I agree. We need to add back
tuple destructuring in closure parameter lists because this is a serious
usability regression. If we're reluctant to just "do the right thing" to
handle the ambiguity of (a,b), we should at least allow it via unambiguous
syntax like ((a,b)). I do think that we should just "do the right thing",
however, with my biggest concern being whether there's any reasonable way
to achieve that in 4.0.

Closure parameter lists are unfortunately only half of the equation here.
This change also regresses the usability of point-free expression.

The consequences for point-free style were expected and cannot really be eliminated without substantially weakening SE-0110. Closure convenience seems
to me to be a much more serious regression.

John, do you also want to say "and without weakening SE-0066"? Because, if I
understand correctly, in this case:

No. SE-0066 was ultimately just a syntax proposal. SE-0110 clarified the

----------
Proposed solution

Parentheses will be required in *function types*. Examples:

Int -> Int // error
(Int) -> Int // function from Int to Int
((Int)) -> Int // also function from Int to Int

Int, Int -> Int // error
(Int, Int) -> Int // function from Int and Int to Int
((Int, Int)) -> Int // function from tuple (Int, Int) to Int
...
----------

intended type-system behavior of function types for both calls and conversions.
Removing tuple destructuring from parameter clauses was an incidental consequence
of that, which in my opinion was pursued out of enthusiasm and without adequately
understanding the impact on closures.

Well, I still believe SE-0110 was inspired by SE-0066 in a way how function types were described in the latter.

Do you want to say, that if SE-0110 was not accepted(but SE-0066 is accepted), function that takes one tuple parameter for example func foo(_ x: (Int, Int)){} will still be of *type* (Int,Int)->Void and not ((Int,Int))->Void ?

And I'd still ask, if you have some time, please answer the question regarding the closure type and allowed code(in the same email you replied just below) in case we revisit SE-0110 *or* SE-0110 was not ever be proposed&accepted.
(I'm afraid I'm missing something very important about SE-0066 and function type differences we should have in Swift 4). Thank you.

Vladimir.

I understand that there are developers who dislike SE-0110's impact on certain
kinds of functional programming, but that is a very broad complaint that is
unlikely to reach consensus or acceptance, especially for Swift 4. In contrast, I
think we may be able to gain consensus on a more targeted proposal that just
re-admits tuple destructuring in closures, assuming we can find an acceptable
implementation.

John.

func add(_ x: Int, _ y: Int) -> Int { return x + y }

zip([1, 2, 3], [4, 5, 6]).map(add)

.. we have a clear function type mismatch situation, when map() expects function
of type ((Int, Int))->Int, but function of type (Int,Int)->Int is provided ? So
probably the additional 'reason' of the 'problem' in this case is SE-0066, no? Or I don't understand the SE-0066 correctly.. Do we want to allow implicit
conversions between function type ((Int,Int))->Int and (Int,Int)->Int?

Quote from SE-0066: --- (Int, Int) -> Int // function from Int and Int to
Int ((Int, Int)) -> Int // function from tuple (Int, Int) to Int ---

During this discussion I see a wish of some group of developers to just return
back tuple splatting for function/closure arguments, so they can freely send
tuple to function/closure accepting a list of parameters(and probably
vise-versa). Is it worth to follow SE-0066 and SE-0110 as is, i.e. disallow
tuple deconstructing and then, as additive change improve the situation with
tuple splatting/deconstructing later with separate big proposal?

Btw, about the SE-0110 proposal. It was discussed, formally reviewed and
accepted. I expect that its revision also should be formally
proposed/reviewed/accepted to collect a wide range of opinions and thoughts, and
attract the attention of developers in this list to the subject.

Also, if we revisit SE-0110, will this code be allowed?:

func foo(_ callback: ((Int,Int))->Void) {} let mycallback = {(x:Int,
y:Int)->Void in } foo(mycallback)

and

func foo(_ callback: (Int,Int)->Void) {} let mycallback = {(x: (Int, Int))->Void
in } foo(mycallback)

If so, what will be result of this for both cases? :

print(type(of:mycallback)) // (Int,Int)->Void or ((Int,Int))->Void

If allowed, do we want to allow implicit conversion between types
(Int,Int)->Void and ((Int,Int))->Void in both directions? (Hello tuple
splatting?)

John.

func add(_ x: Int, _ y: Int) -> Int { return x + y } zip([1, 2, 3], [4, 5,
6]).map(add) // error: nested tuple parameter '(Int, Int)' of function
'(((_.Element, _.Element)) throws -> _) throws -> [_]' does not support
destructuring This may not be a common pattern in most projects, but we
heavily use this style in the Kickstarter app in our functional and FRP
code. Definitely not the most common coding pattern, but a very expressive
one that we rely on. Our interim solution is a bunch of overloaded helpers,
e.g.: func tupleUp<A, B, C>(_ f: (A, B) -> C) -> ((A, B)) -> C { return } zip([1, 2, 3], [4, 5, 6]).map(tupleUp(add)) Stephen

.

I understand that there are developers who dislike SE-0110's impact on
certain kinds of functional programming, but that is a very broad complaint
that is unlikely to reach consensus or acceptance, especially for Swift 4.

The impact of SE-0110 as currently implemented in Swift 4 leads to
following migration choice wherever you have a closure that takes tuple
argument:
* concise but obfuscate code ($0.1, ...)
* readable but verbose code (requiring a ton of boilerplate: intermediate
argument, expand signature to include return type, desctructure tuple on
new line using let, add return clause)

Maybe I misunderstood you, but I don't think this is marginal issue
affecting only some "developers that dislike the impact on certain kinds of
functional programming".

This is a HUGE regression to the usability of all closure with tuple
arguments. I'd wager that is the prevailing consensual opinion of anyone
who has experienced this issue in practice.

I would go as far as to claim that current implementation of SE-0110 does
not conform to the Swift 4's goal of backwards source compatibility.

In contrast, I think we may be able to gain consensus on a more targeted
proposal that just re-admits tuple destructuring in closures, assuming we
can find an acceptable implementation.

From what I understand our options are quite limited because of tight

constraints of Swift 4's emphasis on backwards source-compatibility and
impending release deadline:

* effectively revert SE-0110 by always using the codepath for Swift 3
compatibility mode
* implement full tuple destructuring (unexplored issues with syntax and
backwards compatibility)

(Note that Swift 3 didn't support full tuple destructuring - SR-4738
<https://bugs.swift.org/browse/SR-4738&gt;\)

--Pavol

···

On Thu, Jun 1, 2017 at 8:52 PM, John McCall via swift-evolution < swift-evolution@swift.org> wrote:

Well, from my point of view closure deserves some special syntax and rules.

Not sure I agree with that. Closures are just anonymous functions and so IMO their syntax should differ from function syntax as little as possible while retaining clarity. A named function call is written f(x, y) or g((x, y)). Immediately you know which has two arguments and which has a 2-tuple as its sole argument. I'm suggesting that for closures we merely transplant this syntax.

The only question (for me) is whether to include everything including the outer parentheses, or just everything between the parentheses. I would prefer to include everything including the outer parentheses because of the additional clarity (and IMO aesthetic quality) of making this syntax parallel the syntax enforced in SE 0066 and 0110:
let f1: (Int, Int) -> Double = { (x, y) in ...}
let f2: ((Int, Int)) -> Double = { ((x, y)) in ...}

The closures mirror their declarations, which (if implemented) would make it clear that the closure preambles are correct and will work as expected. It's just one fewer thing to have to think about.

···

On May 29, 2017, at 2:42 PM, Vladimir.S <svabox@gmail.com> wrote:

Well, from my point of view closure deserves some special syntax and rules.

I don’t.

I want the compile to infer from context whether “{ (x, y) in }” is of type
“(_, _) -> _” with the optional parentheses included, or of type “((_, _))
-> _” with the optional parentheses elided. This is similar to how we use
context to determine whether “1” is of type Int or Double or something else
entirely.

If a closure appears in a context where it can only accept a tuple, such as
mapping a dictionary, then obviously its type should have a tuple
parameter. If a closure appears in a context where it can only accept two
arguments, then obviously its type should have two parameters.

I want the compiler to figure out, when possible from context, whether the
optional parentheses were included, so it “just works” for developers.

Nevin

···

On Mon, May 29, 2017 at 3:18 PM, Vladimir.S <svabox@gmail.com> wrote:

On 29.05.2017 21:08, Nevin Brackett-Rozinsky wrote:

barTuple{ (x, y) in } // valid, destructuring one tuple
barTuple{ ((x, y)) in } // valid, destructuring one tuple with optional
parentheses

Here is the problem for me. According to SE-0066 closure declared as
{(x,y) in} should be of type (Int,Int)->() and not ((Int,Int))->().
Why do you want to be able to pass wrong function/closure type into
barTuple?

I think the goal of SE 0110 and to a lesser extent 0066 was to disallow this level of intelligence in the compiler. While technically feasible, it's not desirable to overload parentheses in this manner.

···

On May 29, 2017, at 3:41 PM, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

On Mon, May 29, 2017 at 3:18 PM, Vladimir.S <svabox@gmail.com> wrote:

On 29.05.2017 21:08, Nevin Brackett-Rozinsky wrote:
barTuple{ (x, y) in } // valid, destructuring one tuple
barTuple{ ((x, y)) in } // valid, destructuring one tuple with optional parentheses

Here is the problem for me. According to SE-0066 closure declared as {(x,y) in} should be of type (Int,Int)->() and not ((Int,Int))->().
Why do you want to be able to pass wrong function/closure type into barTuple?

I don’t.

I want the compile to infer from context whether “{ (x, y) in }” is of type “(_, _) -> _” with the optional parentheses included, or of type “((_, _)) -> _” with the optional parentheses elided. This is similar to how we use context to determine whether “1” is of type Int or Double or something else entirely.

If a closure appears in a context where it can only accept a tuple, such as mapping a dictionary, then obviously its type should have a tuple parameter. If a closure appears in a context where it can only accept two arguments, then obviously its type should have two parameters.

I want the compiler to figure out, when possible from context, whether the optional parentheses were included, so it “just works” for developers.

Nevin

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

I understand that there are developers who dislike SE-0110's impact on certain kinds of functional programming, but that is a very broad complaint that is unlikely to reach consensus or acceptance, especially for Swift 4.

The impact of SE-0110 as currently implemented in Swift 4 leads to following migration choice wherever you have a closure that takes tuple argument:
* concise but obfuscate code ($0.1, ...)
* readable but verbose code (requiring a ton of boilerplate: intermediate argument, expand signature to include return type, desctructure tuple on new line using let, add return clause)

Maybe I misunderstood you, but I don't think this is marginal issue affecting only some "developers that dislike the impact on certain kinds of functional programming".

You're misunderstanding me. I have explicitly said, several times, that I agree that the impact on tuple destructuring in closures is a serious regression. There have *also* been objections to losing argument-splat behavior, and while that does negatively affect some functional styles, I think it would be a mistake to try to address that now.

John.

···

On Jun 1, 2017, at 2:39 PM, Pavol Vaskovic <pali@pali.sk> wrote:
On Thu, Jun 1, 2017 at 8:52 PM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This is a HUGE regression to the usability of all closure with tuple arguments. I'd wager that is the prevailing consensual opinion of anyone who has experienced this issue in practice.

I would go as far as to claim that current implementation of SE-0110 does not conform to the Swift 4's goal of backwards source compatibility.

In contrast, I think we may be able to gain consensus on a more targeted proposal that just re-admits tuple destructuring in closures, assuming we can find an acceptable implementation.

From what I understand our options are quite limited because of tight constraints of Swift 4's emphasis on backwards source-compatibility and impending release deadline:

* effectively revert SE-0110 by always using the codepath for Swift 3 compatibility mode
* implement full tuple destructuring (unexplored issues with syntax and backwards compatibility)

(Note that Swift 3 didn't support full tuple destructuring - SR-4738 <https://bugs.swift.org/browse/SR-4738&gt;\)

--Pavol

I vote to revert it to current behavior. Then we can take time to come up with the correct answer for future Swift.

If we don’t revert and then end up changing it again later, we will break everybody’s code twice...

Thanks,
Jon

···

On Jun 1, 2017, at 2:39 PM, Pavol Vaskovic via swift-evolution <swift-evolution@swift.org> wrote:

On Thu, Jun 1, 2017 at 8:52 PM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I understand that there are developers who dislike SE-0110's impact on certain kinds of functional programming, but that is a very broad complaint that is unlikely to reach consensus or acceptance, especially for Swift 4.

The impact of SE-0110 as currently implemented in Swift 4 leads to following migration choice wherever you have a closure that takes tuple argument:
* concise but obfuscate code ($0.1, ...)
* readable but verbose code (requiring a ton of boilerplate: intermediate argument, expand signature to include return type, desctructure tuple on new line using let, add return clause)

Maybe I misunderstood you, but I don't think this is marginal issue affecting only some "developers that dislike the impact on certain kinds of functional programming".

This is a HUGE regression to the usability of all closure with tuple arguments. I'd wager that is the prevailing consensual opinion of anyone who has experienced this issue in practice.

I would go as far as to claim that current implementation of SE-0110 does not conform to the Swift 4's goal of backwards source compatibility.

In contrast, I think we may be able to gain consensus on a more targeted proposal that just re-admits tuple destructuring in closures, assuming we can find an acceptable implementation.

From what I understand our options are quite limited because of tight constraints of Swift 4's emphasis on backwards source-compatibility and impending release deadline:

* effectively revert SE-0110 by always using the codepath for Swift 3 compatibility mode
* implement full tuple destructuring (unexplored issues with syntax and backwards compatibility)

(Note that Swift 3 didn't support full tuple destructuring - SR-4738 <https://bugs.swift.org/browse/SR-4738&gt;\)

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

+1 This seems to be the nub of the solution. It would still be a source compatibility breaking change
but only double bracketed syntax can satisfy the anti-ambiguists and the single-line-expressivists
and could apply to tuples in general. The current state requiring an intermediate variable is terrible.

-John

···

On 1 Jun 2017, at 17:31, Tommaso Piazza via swift-evolution <swift-evolution@swift.org> wrote:

>>> On May 28, 2017, at 7:04 PM, John McCall via swift-evolution > >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>>
>>> Yes, I agree. We need to add back tuple destructuring in closure parameter
>>> lists because this is a serious usability regression. If we're reluctant to
>>> just "do the right thing" to handle the ambiguity of (a,b), we should at least
>>> allow it via unambiguous syntax like ((a,b)). I do think that we should just
>>> "do the right thing", however, with my biggest concern being whether there's
>>> any reasonable way to achieve that in 4.0.

Dear all,

I made a comparison of Swift's 4 lack of tuple unsplatting, here is how it stands in comparison with other languages

https://gist.github.com/blender/53f9568617654c38a219dd4a8353d935

Thank you! Very useful information. And also I really like the opinion of @AliSoftware in comments for this article.

I'd suggest to add this variant to Swift section in your article:

let eighteenOrMore = ["Tom" : 33, "Rebecca" : 17, "Siri" : 5].filter {
  (arg: (name: String, age: Int)) in arg.age >= 18 }

(I believe it is better that 2 others Swift variants.)

It seems for me that we need to allow some special syntax for *explicit* tuple destructuring in closures to make all happy.

FWIW These suggestions are my favorite:

1. Just allow type inference for tuple's destructured variables in this position:

.filter { (arg: (name, age)) in arg.age >= 18 }

2. (1) + allow underscore for tuple argument name:

.filter { (_: (name, age)) in age >= 18 }

3. (2) + allow to omit parenthesis (probably only in case of just one tuple argument)

.filter { _: (name, age) in age >= 18 }

4. Use pattern matching syntax:

.filter { case let (name, age) in age >= 18 }

(looks similar as allowed today: if case let (name, age) = x { print(name, age) } )

5. Use two pairs of parenthesis :

.filter { ((name, age)) in age >= 18 }

Btw, about the 5th variant. If took what is allowed today:
.filter { (arg: (name: String, age: Int)) in arg.age >= 18 }
, and allow type inference for tuple part arguments, we'll have this:
.filter { (arg: (name, age)) in arg.age >= 18 }
, and if additionally allow skipping of tuple argument declaration we'll have:
.filter { ((name, age)) in arg.age >= 18 }
I.e. two pairs for parenthesis for tuple destructuring, and such syntax is similar to the type this closure should have : ((String, Int)) -> Bool

···

On 01.06.2017 19:31, Tommaso Piazza wrote:

On Thursday, June 1, 2017 12:25 PM, Vladimir.S via swift-evolution > <swift-evolution@swift.org> wrote:

On 01.06.2017 0:42, John McCall wrote:
>> On May 31, 2017, at 2:02 PM, Stephen Celis <stephen.celis@gmail.com > <mailto:stephen.celis@gmail.com>> wrote:
>>> On May 28, 2017, at 7:04 PM, John McCall via swift-evolution > >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>>
>>> Yes, I agree. We need to add back tuple destructuring in closure parameter
>>> lists because this is a serious usability regression. If we're reluctant to
>>> just "do the right thing" to handle the ambiguity of (a,b), we should at least
>>> allow it via unambiguous syntax like ((a,b)). I do think that we should just
>>> "do the right thing", however, with my biggest concern being whether there's
>>> any reasonable way to achieve that in 4.0.
>>
>> Closure parameter lists are unfortunately only half of the equation here. This
>> change also regresses the usability of point-free expression.
>
> The consequences for point-free style were expected and cannot really be
> eliminated without substantially weakening SE-0110. Closure convenience seems to
> me to be a much more serious regression.

John, do you also want to say "and without weakening SE-0066"? Because, if I
understand correctly, in this case:

   func add(_ x: Int, _ y: Int) -> Int {
     return x + y
   }

   zip([1, 2, 3], [4, 5, 6]).map(add)

.. we have a clear function type mismatch situation, when map() expects function of
type ((Int, Int))->Int, but function of type (Int,Int)->Int is provided ? So probably
the additional 'reason' of the 'problem' in this case is SE-0066, no?
Or I don't understand the SE-0066 correctly..
Do we want to allow implicit conversions between function type ((Int,Int))->Int and
(Int,Int)->Int?

Quote from SE-0066:
---
(Int, Int) -> Int // function from Int and Int to Int
((Int, Int)) -> Int // function from tuple (Int, Int) to Int
---

During this discussion I see a wish of some group of developers to just return back
tuple splatting for function/closure arguments, so they can freely send tuple to
function/closure accepting a list of parameters(and probably vise-versa).
Is it worth to follow SE-0066 and SE-0110 as is, i.e. disallow tuple deconstructing
and then, as additive change improve the situation with tuple
splatting/deconstructing later with separate big proposal?

Btw, about the SE-0110 proposal. It was discussed, formally reviewed and accepted. I
expect that its revision also should be formally proposed/reviewed/accepted to
collect a wide range of opinions and thoughts, and attract the attention of
developers in this list to the subject.

Also, if we revisit SE-0110, will this code be allowed?:

func foo(_ callback: ((Int,Int))->Void) {}
let mycallback = {(x:Int, y:Int)->Void in }
foo(mycallback)

and

func foo(_ callback: (Int,Int)->Void) {}
let mycallback = {(x: (Int, Int))->Void in }
foo(mycallback)

If so, what will be result of this for both cases? :

print(type(of:mycallback)) // (Int,Int)->Void or ((Int,Int))->Void

If allowed, do we want to allow implicit conversion between types (Int,Int)->Void and
((Int,Int))->Void in both directions? (Hello tuple splatting?)

>
> John.
>
>>
>> func add(_ x: Int, _ y: Int) -> Int { return x + y }
>>
>> zip([1, 2, 3], [4, 5, 6]).map(add)
>>
>> // error: nested tuple parameter '(Int, Int)' of function '(((_.Element,
>> _.Element)) throws -> _) throws -> [_]' does not support destructuring
>>
>> This may not be a common pattern in most projects, but we heavily use this style
>> in the Kickstarter app in our functional and FRP code. Definitely not the most
>> common coding pattern, but a very expressive one that we rely on.
>>
>> Our interim solution is a bunch of overloaded helpers, e.g.:
>>
>> func tupleUp<A, B, C>(_ f: (A, B) -> C) -> ((A, B)) -> C { return }
>>
>> zip([1, 2, 3], [4, 5, 6]).map(tupleUp(add))
>>
>> Stephen
>
> .
>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Ah, now I understand. You were talking about the example brought up by
Stephen Celis, where they `map` over tupples from zipped sequence with
`add` function that takes two arguments and the they had to adapt with
`tuppleUp` function.

I'm sorry for the misunderstanding!

--Pavol

···

On Fri, Jun 2, 2017 at 12:06 AM, John McCall <rjmccall@apple.com> wrote:

You're misunderstanding me. I have explicitly said, several times, that I
agree that the impact on tuple destructuring in closures is a serious
regression. There have *also* been objections to losing argument-splat
behavior, and while that does negatively affect some functional styles, I
think it would be a mistake to try to address that now.

+1

This source-breaking change affects lots of libraries, as it has been
shown. It seems very late in Swift 4 to be discussing alternative syntaxes,
so the best action for me would be revert it and revisit this in the next
version.

···

On Fri, Jun 2, 2017 at 1:53 AM, Jonathan Hull via swift-evolution < swift-evolution@swift.org> wrote:

I vote to revert it to current behavior. Then we can take time to come up
with the correct answer for future Swift.

If we don’t revert and then end up changing it again later, we will break
everybody’s code twice...

Thanks,
Jon

--
Víctor Pimentel

John and others, who are involved into this thread.
I'd suggest to clarify the current(Swift4) situation with function/closure type before we make any other decision and before continue to discuss the subject.
(sorry for long email, but I feel the problem with SE-0110 is wider than just "let's just drop it")

Please review this code and its result (swift-4.0-DEVELOPMENT-SNAPSHOT-2017-06-01-a-osx) :

func fooParam(_ x: Int, _ y: Int){}
func fooTuple(_ x: (Int, Int)) {}

print("type of fooParam is", type(of:fooParam))
// type of fooParam is (Int, Int) -> ()

print("type of fooTuple is", type(of:fooTuple))
// type of fooTuple is (Int, Int) -> ()

print("type of fooParam == type of fooTuple ?", type(of: fooParam) == type(of: fooTuple))
// true

if fooParam is (Int,Int)->() { print("fooParam is (Int,Int)->()") }
// fooParam is (Int,Int)->()

if fooParam is ((Int,Int))->() { print("fooParam is ((Int,Int))->()") }
// fooParam is ((Int,Int))->()

if fooTuple is (Int,Int)->() { print("fooTuple is (Int,Int)->()") }
// fooTuple is (Int,Int)->()

if fooTuple is ((Int,Int))->() { print("fooTuple is ((Int,Int))->()") }
// fooTuple is ((Int,Int))->()

var closureParam = { (x: Int, y: Int) in }
var closureTuple = { (x: (Int, Int)) in }

print("type of closureParam is", type(of:closureParam))
// type of closureParam is (Int, Int) -> ()

print("type of closureTuple is", type(of:closureTuple))
// type of closureTuple is (Int, Int) -> ()

if closureParam is (Int,Int)->() { print("closureParam is (Int,Int)->()") }
// closureParam is (Int,Int)->()

if closureParam is ((Int,Int))->() { print("closureParam is ((Int,Int))->()") }
// closureParam is ((Int,Int))->()

if closureTuple is (Int,Int)->() { print("closureTuple is (Int,Int)->()") }
// closureTuple is (Int,Int)->()

if closureTuple is ((Int,Int))->() { print("closureTuple is ((Int,Int))->()") }
// closureTuple is ((Int,Int))->()

func barParams(_ callback: (Int,Int)->()) { callback(1,2) }

//barParams(fooTuple)
//error: cannot convert value of type '((Int, Int)) -> ()' to expected argument type '(Int, Int) -> ()'

//barParams(closureTuple)
//error: cannot convert value of type '((Int, Int)) -> ()' to expected argument type '(Int, Int) -> ()'

func barTuple(_ callback: ((Int,Int))->()) { let tuple = (1,2); callback(tuple) }

//barTuple(fooParam)
//error: nested tuple parameter '(Int, Int)' of function '(((Int, Int)) -> ()) -> ()' does not support destructuring

//barTuple(closureParam)
//error: cannot convert value of type '(Int, Int) -> ()' to expected argument type '((Int, Int)) -> ()'

So, what is the type of fooParam/fooTuple/closureParam/closureTuple ?
Why the type is different in different situations?
Should we have strong and unambiguous type for functions/closures and probably *then* allow implicit conversion like "(Int,Int)->() can be used when ((Int,Int))->() is required and vise-versa"?

We have all this inconsistency with function/closure types from Swift 2, and this should be actually fixed in Swift 3, but still we are going to keep that in Swift 4.

Personally, I'm not against tuple deconstructing in closures and even think we can just have allowed implicit conversion between func/closure with single tuple argument and a list of arguments. But, I do believe we need consistent state in this area, strong types and clear rules. Because of this, if we have no time to implement good solution/workaround for Swift 4, I believe it is better to keep/fully implement SE-0110 and then provide solution for tuple destructuring. Otherwise we are going to have all these mess with func/closure types for very long time after Swift 4. Or we can try to implement a good workaround before release.

In current Swift 4 we are allowed for this:
.filter {(arg: (name: String, age: Int)) in arg.age >= 18 }

Is it really no way(before Swift 4 is released) to allow type inference for 'name' and 'age'? I believe this could be a good *workaround*, totally in Swift way(just like you can omit types for arguments in closure, or can specify them), this will keep the existed syntax for closure arguments and this will not prevent us from suggesting best solution after the Swift 4 release. And IMO this is acceptable compromise between loosing tuple deconstruction and keep the mess with function/closure type:
.filter {(arg: (name, age)) in arg.age >= 18 }

Or, as I said, just allow implicit conversion between function/closure types taking one tuple argument and a list of arguments. But still .filter should expect exactly ((Int,String))->() but it is allowed to send (Int,String)->() to it. All is strongly typed, but with magic "conversion".

I still believe that SE-0066 was the proposal that answered all these questions and said that (Int,Int)->() and ((Int,Int))->() are two different types. SE-0110 just clarified that the same rule should be applied to closures.
Until we started to discuss revisiting of SE-0110 I was under strong impression that SE-0066 was just partially implemented in Swift 3 and will be fully implemented only in Swift 4.

I even believe that information on SE-0066 page is not correct, that SE was NOT fully implemented for Swift 3 as described in the proposal header on site.
Could someone from core team please clarify, if they really think that proposal is fully implemented in current Swift 4.0 build?

I even ask(please, when you have a moment) Chris Lattner to look into this thread, because of his words in Rationale part of the SE-0110 acceptance message:

"... The community and core team agree that this proposal is the right thing to do, and many agree that this could probably have been treated as a bug fix on a previous proposal.
..."
(https://lists.swift.org/pipermail/swift-evolution-announce/2016-July/000215.html\)

Seems like Chris at that moment also was thinking about SE-0110 just as bug fix for SE-0066/SE-0029 and so probably he can provide some useful information/thoughts about the subject.

Thank you for attention.
Vladimir.

···

On 02.06.2017 1:06, John McCall wrote:

On Jun 1, 2017, at 2:39 PM, Pavol Vaskovic <pali@pali.sk <mailto:pali@pali.sk>> wrote:

On Thu, Jun 1, 2017 at 8:52 PM, John McCall via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

    I understand that there are developers who dislike SE-0110's impact on certain
    kinds of functional programming, but that is a very broad complaint that is
    unlikely to reach consensus or acceptance, especially for Swift 4.

The impact of SE-0110 as currently implemented in Swift 4 leads to following migration choice wherever you have a closure that takes tuple argument:
* concise but obfuscate code ($0.1, ...)
* readable but verbose code (requiring a ton of boilerplate: intermediate argument, expand signature to include return type, desctructure tuple on new line using let, add return clause)

Maybe I misunderstood you, but I don't think this is marginal issue affecting only some "developers that dislike the impact on certain kinds of functional programming".

You're misunderstanding me. I have explicitly said, several times, that I agree that the impact on tuple destructuring in closures is a serious regression. There have *also* been objections to losing argument-splat behavior, and while that does negatively affect some functional styles, I think it would be a mistake to try to address that now.

John.

This is a HUGE regression to the usability of all closure with tuple arguments. I'd wager that is the prevailing consensual opinion of anyone who has experienced this issue in practice.

I would go as far as to claim that current implementation of SE-0110 does not conform to the Swift 4's goal of backwards source compatibility.

    In contrast, I think we may be able to gain consensus on a more targeted
    proposal that just re-admits tuple destructuring in closures, assuming we can
    find an acceptable implementation.

From what I understand our options are quite limited because of tight constraints of Swift 4's emphasis on backwards source-compatibility and impending release deadline:

* effectively revert SE-0110 by always using the codepath for Swift 3 compatibility mode
* implement full tuple destructuring (unexplored issues with syntax and backwards compatibility)

(Note that Swift 3 didn't support full tuple destructuring - SR-4738 <https://bugs.swift.org/browse/SR-4738&gt;\)

--Pavol

I agree with both points: we need to fix the type checker semantics+performance regression, but I also sympathize with the beauty regression for closures. Here are some the examples Gwendal Roué cited up-thread (just to make the discussion concrete):

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() }

One way to split the difference here is to eliminate the splatting behavior, but keep the destructuring (irrefutable pattern matching) behavior as well. In these cases, just require an extra explicit paren for the parameter list. This would change the diff's to:

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

Example 2 :
- .map { (mappedColumn, baseColumn) -> (Int, String) in
+ .map { ((mappedColumn, baseColumn)) -> (Int, String) in

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

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

What do you think? Seems like it would solve the type checker problem, uglify the code a lot less, and make the fixit/migration happily trivial.

-Chris

···

On Jun 1, 2017, at 3:06 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 1, 2017, at 2:39 PM, Pavol Vaskovic <pali@pali.sk <mailto:pali@pali.sk>> wrote:

On Thu, Jun 1, 2017 at 8:52 PM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I understand that there are developers who dislike SE-0110's impact on certain kinds of functional programming, but that is a very broad complaint that is unlikely to reach consensus or acceptance, especially for Swift 4.

The impact of SE-0110 as currently implemented in Swift 4 leads to following migration choice wherever you have a closure that takes tuple argument:
* concise but obfuscate code ($0.1, ...)
* readable but verbose code (requiring a ton of boilerplate: intermediate argument, expand signature to include return type, desctructure tuple on new line using let, add return clause)

Maybe I misunderstood you, but I don't think this is marginal issue affecting only some "developers that dislike the impact on certain kinds of functional programming".

You're misunderstanding me. I have explicitly said, several times, that I agree that the impact on tuple destructuring in closures is a serious regression. There have *also* been objections to losing argument-splat behavior, and while that does negatively affect some functional styles, I think it would be a mistake to try to address that now.

I think the goal of SE 0110 and to a lesser extent 0066 was to disallow
this level of intelligence in the compiler.

Interesting.

I happen to think that the goal of SE–110
<https://github.com/apple/swift-evolution/blob/master/proposals/0110-distingish-single-tuple-arg.md&gt;
was to make Swift's type system “properly distinguish between functions
that take one tuple argument, and functions that take multiple arguments.”
Nowhere does that proposal discuss tuple destructuring, and nowhere does it
discuss optional parentheses around closure parameter lists.

I might go so far as to say that any commits which *do* affect those
things, cannot possibly be correct implementations of the accepted proposal
SE–110, because SE–110 did not describe any changes there.

With SE–66
<https://github.com/apple/swift-evolution/blob/master/proposals/0066-standardize-function-type-syntax.md&gt;
the case is even more clearcut: that proposal explicitly addresses the
question, “Should we require parentheses in closure expression parameter
lists?“ and answers it in the negative. The core team’s notes
<https://lists.swift.org/pipermail/swift-evolution-announce/2016-May/000138.html&gt;
on accepting also specify, “The core team did not feel that this proposal
needed to include required parentheses within closures, which have their
own fairly specific grammar.”

While technically feasible, it's not desirable to overload parentheses in

this manner.

I strongly disagree. Language features are desirable exactly to the extent
that they make life better for developers.

Moreover, parentheses are *already* optional in closure parameter lists.
Making them mandatory would be source-breaking for no benefit to
programmers. Plus having to write double-parentheses in “dict.map{ ((key,
value)) in … }” would be needlessly annoying.

In my view there have been far too many calls for making Swift “consistent”
in ways that actually make it less enjoyable to use. That is the opposite
of “Swifty”, and we should instead prioritize convenience for users above
rigid consistency of implementation.

In the case at hand, with Dictionary.map, the vast majority of the time the
user doesn’t actually care whether the closure takes two arguments or a
single 2-tuple argument. They just know that it takes a key and a value,
and they want to be able to write “dict.map{ (key, value) in … }”.

Sure, the closure *does* take a 2-tuple, and it does not take two
arguments, but the programmer *using* it shouldn’t have to bother about
that distinction most of the time. They just want to assign the key to one
identifier and the value to another. If they try to write “key, value”
without any parentheses the compiler will complain and they’ll add the
parens. But if the compiler demands a *second* set of parentheses, that
will just seem ridiculous.

Nevin

···

On Mon, May 29, 2017 at 10:47 PM, Robert Bennett <rltbennett@icloud.com> wrote:

I, for one, would be willing to accept Xiaodi's suggestion involving
`let`–especially if (pipe dream follows) we could use the same syntax in
functions/methods to destructure parameters.

···

On Thu, Jun 1, 2017 at 3:32 PM, Vladimir.S via swift-evolution < swift-evolution@swift.org> wrote:

On 01.06.2017 19:31, Tommaso Piazza wrote:

Dear all,

I made a comparison of Swift's 4 lack of tuple unsplatting, here is how
it stands in comparison with other languages

https://gist.github.com/blender/53f9568617654c38a219dd4a8353d935

Thank you! Very useful information. And also I really like the opinion of
@AliSoftware in comments for this article.

I'd suggest to add this variant to Swift section in your article:

let eighteenOrMore = ["Tom" : 33, "Rebecca" : 17, "Siri" : 5].filter {
        (arg: (name: String, age: Int)) in arg.age >= 18 }

(I believe it is better that 2 others Swift variants.)

It seems for me that we need to allow some special syntax for *explicit*
tuple destructuring in closures to make all happy.

FWIW These suggestions are my favorite:

1. Just allow type inference for tuple's destructured variables in this
position:

.filter { (arg: (name, age)) in arg.age >= 18 }

2. (1) + allow underscore for tuple argument name:

.filter { (_: (name, age)) in age >= 18 }

3. (2) + allow to omit parenthesis (probably only in case of just one
tuple argument)

.filter { _: (name, age) in age >= 18 }

4. Use pattern matching syntax:

.filter { case let (name, age) in age >= 18 }

(looks similar as allowed today: if case let (name, age) = x { print(name,
age) } )

5. Use two pairs of parenthesis :

.filter { ((name, age)) in age >= 18 }

Btw, about the 5th variant. If took what is allowed today:
.filter { (arg: (name: String, age: Int)) in arg.age >= 18 }
, and allow type inference for tuple part arguments, we'll have this:
.filter { (arg: (name, age)) in arg.age >= 18 }
, and if additionally allow skipping of tuple argument declaration we'll
have:
.filter { ((name, age)) in arg.age >= 18 }
I.e. two pairs for parenthesis for tuple destructuring, and such syntax is
similar to the type this closure should have : ((String, Int)) -> Bool

On Thursday, June 1, 2017 12:25 PM, Vladimir.S via swift-evolution < >> swift-evolution@swift.org> wrote:

On 01.06.2017 0:42, John McCall wrote:
>> On May 31, 2017, at 2:02 PM, Stephen Celis <stephen.celis@gmail.com >> <mailto:stephen.celis@gmail.com>> wrote:
>>> On May 28, 2017, at 7:04 PM, John McCall via swift-evolution >> >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> >> wrote:
>>>
>>> Yes, I agree. We need to add back tuple destructuring in closure
parameter
>>> lists because this is a serious usability regression. If we're
reluctant to
>>> just "do the right thing" to handle the ambiguity of (a,b), we
should at least
>>> allow it via unambiguous syntax like ((a,b)). I do think that we
should just
>>> "do the right thing", however, with my biggest concern being whether
there's
>>> any reasonable way to achieve that in 4.0.
>>
>> Closure parameter lists are unfortunately only half of the equation
here. This
>> change also regresses the usability of point-free expression.
>
> The consequences for point-free style were expected and cannot really
be
> eliminated without substantially weakening SE-0110. Closure
convenience seems to
> me to be a much more serious regression.

John, do you also want to say "and without weakening SE-0066"? Because,
if I
understand correctly, in this case:

   func add(_ x: Int, _ y: Int) -> Int {
     return x + y
   }

   zip([1, 2, 3], [4, 5, 6]).map(add)

.. we have a clear function type mismatch situation, when map() expects
function of
type ((Int, Int))->Int, but function of type (Int,Int)->Int is provided ?
So probably
the additional 'reason' of the 'problem' in this case is SE-0066, no?
Or I don't understand the SE-0066 correctly..
Do we want to allow implicit conversions between function type
((Int,Int))->Int and
(Int,Int)->Int?

Quote from SE-0066:
---
(Int, Int) -> Int // function from Int and Int to Int
((Int, Int)) -> Int // function from tuple (Int, Int) to Int
---

During this discussion I see a wish of some group of developers to just
return back
tuple splatting for function/closure arguments, so they can freely send
tuple to
function/closure accepting a list of parameters(and probably vise-versa).
Is it worth to follow SE-0066 and SE-0110 as is, i.e. disallow tuple
deconstructing
and then, as additive change improve the situation with tuple
splatting/deconstructing later with separate big proposal?

Btw, about the SE-0110 proposal. It was discussed, formally reviewed and
accepted. I
expect that its revision also should be formally
proposed/reviewed/accepted to
collect a wide range of opinions and thoughts, and attract the attention
of
developers in this list to the subject.

Also, if we revisit SE-0110, will this code be allowed?:

func foo(_ callback: ((Int,Int))->Void) {}
let mycallback = {(x:Int, y:Int)->Void in }
foo(mycallback)

and

func foo(_ callback: (Int,Int)->Void) {}
let mycallback = {(x: (Int, Int))->Void in }
foo(mycallback)

If so, what will be result of this for both cases? :

print(type(of:mycallback)) // (Int,Int)->Void or ((Int,Int))->Void

If allowed, do we want to allow implicit conversion between types
(Int,Int)->Void and
((Int,Int))->Void in both directions? (Hello tuple splatting?)

>
> John.
>
>
>>
>> func add(_ x: Int, _ y: Int) -> Int { return x + y }
>>
>> zip([1, 2, 3], [4, 5, 6]).map(add)
>>
>> // error: nested tuple parameter '(Int, Int)' of function
'(((_.Element,
>> _.Element)) throws -> _) throws -> [_]' does not support destructuring
>>
>> This may not be a common pattern in most projects, but we heavily use
this style
>> in the Kickstarter app in our functional and FRP code. Definitely not
the most
>> common coding pattern, but a very expressive one that we rely on.
>>
>> Our interim solution is a bunch of overloaded helpers, e.g.:
>>
>> func tupleUp<A, B, C>(_ f: (A, B) -> C) -> ((A, B)) -> C { return }
>>
>> zip([1, 2, 3], [4, 5, 6]).map(tupleUp(add))
>>
>> Stephen
>
> .
>
_______________________________________________
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
https://lists.swift.org/mailman/listinfo/swift-evolution

To add more real-world project data to this discussion, I just did a test migration of Siesta to Swift 4 using the 2017-05-30 snapshot.

Nothing earth-shattering follows — just more practical evidence that the problem needs attention.

Here’s what I found:

(1) The lack of tuple destructing under discussion here hit the project in half a dozen places. That’s in only a ~2k-line codebase.

(2) Every one of those places it hit involved a Dictionary.

Most involved calling map, flatMap, or filter, so adding Dictionary-specific 2-arg flavors of those methods to stdlib would reduce the burden of SE-110. However, some places involved custom Collection extensions (e.g. “any”), so it’s not just the stdlib that would have to add nearly-redundant dictionary-specific variants of Collection methods. Destructuring still has a clear advantage.

(3) All the problems were indeed fixable using $0.0 and $0.1 in place of named destructured args, but the damage to readability was … severe. Compare this, for example:

    let nonEmptyStages = stages
        .filter { _, stage in !stage.isEmpty }
        .map { key, _ in key }

…to this:

    let nonEmptyStages = stages
        .filter { !$0.1.isEmpty }
        .map { $0.0 }

Even though “stage” conveys little information, and “key” even less, the net gain from those named args is large, at least to my eyes.

(4) SE-110 is also the thing that prevents Siesta’s dependencies from compiling.

Hope all this helps weigh the tradeoffs of taking this on.

Cheers,

Paul

···

On Jun 1, 2017, at 2:32 PM, Vladimir.S via swift-evolution <swift-evolution@swift.org> wrote:

On 01.06.2017 19:31, Tommaso Piazza wrote:

Dear all,
I made a comparison of Swift's 4 lack of tuple unsplatting, here is how it stands in comparison with other languages
https://gist.github.com/blender/53f9568617654c38a219dd4a8353d935

Thank you! Very useful information. And also I really like the opinion of @AliSoftware in comments for this article.

I'd suggest to add this variant to Swift section in your article:

let eighteenOrMore = ["Tom" : 33, "Rebecca" : 17, "Siri" : 5].filter {
  (arg: (name: String, age: Int)) in arg.age >= 18 }

(I believe it is better that 2 others Swift variants.)

It seems for me that we need to allow some special syntax for *explicit* tuple destructuring in closures to make all happy.

FWIW These suggestions are my favorite:

1. Just allow type inference for tuple's destructured variables in this position:

.filter { (arg: (name, age)) in arg.age >= 18 }

2. (1) + allow underscore for tuple argument name:

.filter { (_: (name, age)) in age >= 18 }

3. (2) + allow to omit parenthesis (probably only in case of just one tuple argument)

.filter { _: (name, age) in age >= 18 }

4. Use pattern matching syntax:

.filter { case let (name, age) in age >= 18 }

(looks similar as allowed today: if case let (name, age) = x { print(name, age) } )

5. Use two pairs of parenthesis :

.filter { ((name, age)) in age >= 18 }

Btw, about the 5th variant. If took what is allowed today:
.filter { (arg: (name: String, age: Int)) in arg.age >= 18 }
, and allow type inference for tuple part arguments, we'll have this:
.filter { (arg: (name, age)) in arg.age >= 18 }
, and if additionally allow skipping of tuple argument declaration we'll have:
.filter { ((name, age)) in arg.age >= 18 }
I.e. two pairs for parenthesis for tuple destructuring, and such syntax is similar to the type this closure should have : ((String, Int)) -> Bool

On Thursday, June 1, 2017 12:25 PM, Vladimir.S via swift-evolution <swift-evolution@swift.org> wrote:
On 01.06.2017 0:42, John McCall wrote:
>> On May 31, 2017, at 2:02 PM, Stephen Celis <stephen.celis@gmail.com <mailto:stephen.celis@gmail.com>> wrote:
>>> On May 28, 2017, at 7:04 PM, John McCall via swift-evolution >> >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>>
>>> Yes, I agree. We need to add back tuple destructuring in closure parameter
>>> lists because this is a serious usability regression. If we're reluctant to
>>> just "do the right thing" to handle the ambiguity of (a,b), we should at least
>>> allow it via unambiguous syntax like ((a,b)). I do think that we should just
>>> "do the right thing", however, with my biggest concern being whether there's
>>> any reasonable way to achieve that in 4.0.
>>
>> Closure parameter lists are unfortunately only half of the equation here. This
>> change also regresses the usability of point-free expression.
>
> The consequences for point-free style were expected and cannot really be
> eliminated without substantially weakening SE-0110. Closure convenience seems to
> me to be a much more serious regression.
John, do you also want to say "and without weakening SE-0066"? Because, if I
understand correctly, in this case:
  func add(_ x: Int, _ y: Int) -> Int {
    return x + y
  }
  zip([1, 2, 3], [4, 5, 6]).map(add)
.. we have a clear function type mismatch situation, when map() expects function of
type ((Int, Int))->Int, but function of type (Int,Int)->Int is provided ? So probably
the additional 'reason' of the 'problem' in this case is SE-0066, no?
Or I don't understand the SE-0066 correctly..
Do we want to allow implicit conversions between function type ((Int,Int))->Int and
(Int,Int)->Int?
Quote from SE-0066:
---
(Int, Int) -> Int // function from Int and Int to Int
((Int, Int)) -> Int // function from tuple (Int, Int) to Int
---
During this discussion I see a wish of some group of developers to just return back
tuple splatting for function/closure arguments, so they can freely send tuple to
function/closure accepting a list of parameters(and probably vise-versa).
Is it worth to follow SE-0066 and SE-0110 as is, i.e. disallow tuple deconstructing
and then, as additive change improve the situation with tuple
splatting/deconstructing later with separate big proposal?
Btw, about the SE-0110 proposal. It was discussed, formally reviewed and accepted. I
expect that its revision also should be formally proposed/reviewed/accepted to
collect a wide range of opinions and thoughts, and attract the attention of
developers in this list to the subject.
Also, if we revisit SE-0110, will this code be allowed?:
func foo(_ callback: ((Int,Int))->Void) {}
let mycallback = {(x:Int, y:Int)->Void in }
foo(mycallback)
and
func foo(_ callback: (Int,Int)->Void) {}
let mycallback = {(x: (Int, Int))->Void in }
foo(mycallback)
If so, what will be result of this for both cases? :
print(type(of:mycallback)) // (Int,Int)->Void or ((Int,Int))->Void
If allowed, do we want to allow implicit conversion between types (Int,Int)->Void and
((Int,Int))->Void in both directions? (Hello tuple splatting?)
>
> John.
>
>
>>
>> func add(_ x: Int, _ y: Int) -> Int { return x + y }
>>
>> zip([1, 2, 3], [4, 5, 6]).map(add)
>>
>> // error: nested tuple parameter '(Int, Int)' of function '(((_.Element,
>> _.Element)) throws -> _) throws -> [_]' does not support destructuring
>>
>> This may not be a common pattern in most projects, but we heavily use this style
>> in the Kickstarter app in our functional and FRP code. Definitely not the most
>> common coding pattern, but a very expressive one that we rely on.
>>
>> Our interim solution is a bunch of overloaded helpers, e.g.:
>>
>> func tupleUp<A, B, C>(_ f: (A, B) -> C) -> ((A, B)) -> C { return }
>>
>> zip([1, 2, 3], [4, 5, 6]).map(tupleUp(add))
>>
>> Stephen
>
> .
>
_______________________________________________
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
https://lists.swift.org/mailman/listinfo/swift-evolution

Yes, Xiaodi's suggestion also was very attractive. Just to remind:

···

On 01.06.2017 22:46, T.J. Usiyan wrote:

I, for one, would be willing to accept Xiaodi's suggestion involving `let`–especially if (pipe dream follows) we could use the same syntax in functions/methods to destructure parameters.

----------
{ (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, Jun 1, 2017 at 3:32 PM, Vladimir.S via swift-evolution > <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

    On 01.06.2017 19:31, Tommaso Piazza wrote:

        Dear all,

        I made a comparison of Swift's 4 lack of tuple unsplatting, here is how it
        stands in comparison with other languages

        https://gist.github.com/blender/53f9568617654c38a219dd4a8353d935
        <https://gist.github.com/blender/53f9568617654c38a219dd4a8353d935&gt;

    Thank you! Very useful information. And also I really like the opinion of
    @AliSoftware in comments for this article.

    I'd suggest to add this variant to Swift section in your article:

    let eighteenOrMore = ["Tom" : 33, "Rebecca" : 17, "Siri" : 5].filter {
             (arg: (name: String, age: Int)) in arg.age >= 18 }

    (I believe it is better that 2 others Swift variants.)

    It seems for me that we need to allow some special syntax for *explicit* tuple
    destructuring in closures to make all happy.

    FWIW These suggestions are my favorite:

    1. Just allow type inference for tuple's destructured variables in this position:

    .filter { (arg: (name, age)) in arg.age >= 18 }

    2. (1) + allow underscore for tuple argument name:

    .filter { (_: (name, age)) in age >= 18 }

    3. (2) + allow to omit parenthesis (probably only in case of just one tuple argument)

    .filter { _: (name, age) in age >= 18 }

    4. Use pattern matching syntax:

    .filter { case let (name, age) in age >= 18 }

    (looks similar as allowed today: if case let (name, age) = x { print(name, age) } )

    5. Use two pairs of parenthesis :

    .filter { ((name, age)) in age >= 18 }

    Btw, about the 5th variant. If took what is allowed today:
    .filter { (arg: (name: String, age: Int)) in arg.age >= 18 }
    , and allow type inference for tuple part arguments, we'll have this:
    .filter { (arg: (name, age)) in arg.age >= 18 }
    , and if additionally allow skipping of tuple argument declaration we'll have:
    .filter { ((name, age)) in arg.age >= 18 }
    I.e. two pairs for parenthesis for tuple destructuring, and such syntax is
    similar to the type this closure should have : ((String, Int)) -> Bool

        On Thursday, June 1, 2017 12:25 PM, Vladimir.S via swift-evolution > <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

        On 01.06.2017 0:42, John McCall wrote:
          >> On May 31, 2017, at 2:02 PM, Stephen Celis <stephen.celis@gmail.com > <mailto:stephen.celis@gmail.com> <mailto:stephen.celis@gmail.com > <mailto:stephen.celis@gmail.com>>> wrote:
          >>> On May 28, 2017, at 7:04 PM, John McCall via swift-evolution > >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org> > <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>> wrote:
          >>>
          >>> Yes, I agree. We need to add back tuple destructuring in closure parameter
          >>> lists because this is a serious usability regression. If we're
        reluctant to
          >>> just "do the right thing" to handle the ambiguity of (a,b), we should
        at least
          >>> allow it via unambiguous syntax like ((a,b)). I do think that we
        should just
          >>> "do the right thing", however, with my biggest concern being whether
        there's
          >>> any reasonable way to achieve that in 4.0.
          >>
          >> Closure parameter lists are unfortunately only half of the equation
        here. This
          >> change also regresses the usability of point-free expression.
          >
          > The consequences for point-free style were expected and cannot really be
          > eliminated without substantially weakening SE-0110. Closure convenience
        seems to
          > me to be a much more serious regression.

        John, do you also want to say "and without weakening SE-0066"? Because, if I
        understand correctly, in this case:

            func add(_ x: Int, _ y: Int) -> Int {
              return x + y
            }

            zip([1, 2, 3], [4, 5, 6]).map(add)

        .. we have a clear function type mismatch situation, when map() expects
        function of
        type ((Int, Int))->Int, but function of type (Int,Int)->Int is provided ? So
        probably
        the additional 'reason' of the 'problem' in this case is SE-0066, no?
        Or I don't understand the SE-0066 correctly..
        Do we want to allow implicit conversions between function type
        ((Int,Int))->Int and
        (Int,Int)->Int?

        Quote from SE-0066:
        ---
        (Int, Int) -> Int // function from Int and Int to Int
        ((Int, Int)) -> Int // function from tuple (Int, Int) to Int
        ---

        During this discussion I see a wish of some group of developers to just
        return back
        tuple splatting for function/closure arguments, so they can freely send tuple to
        function/closure accepting a list of parameters(and probably vise-versa).
        Is it worth to follow SE-0066 and SE-0110 as is, i.e. disallow tuple
        deconstructing
        and then, as additive change improve the situation with tuple
        splatting/deconstructing later with separate big proposal?

        Btw, about the SE-0110 proposal. It was discussed, formally reviewed and
        accepted. I
        expect that its revision also should be formally proposed/reviewed/accepted to
        collect a wide range of opinions and thoughts, and attract the attention of
        developers in this list to the subject.

        Also, if we revisit SE-0110, will this code be allowed?:

        func foo(_ callback: ((Int,Int))->Void) {}
        let mycallback = {(x:Int, y:Int)->Void in }
        foo(mycallback)

        and

        func foo(_ callback: (Int,Int)->Void) {}
        let mycallback = {(x: (Int, Int))->Void in }
        foo(mycallback)

        If so, what will be result of this for both cases? :

        print(type(of:mycallback)) // (Int,Int)->Void or ((Int,Int))->Void

        If allowed, do we want to allow implicit conversion between types
        (Int,Int)->Void and
        ((Int,Int))->Void in both directions? (Hello tuple splatting?)

          >
          > John.
          >
          >>
          >> func add(_ x: Int, _ y: Int) -> Int { return x + y }
          >>
          >> zip([1, 2, 3], [4, 5, 6]).map(add)
          >>
          >> // error: nested tuple parameter '(Int, Int)' of function '(((_.Element,
          >> _.Element)) throws -> _) throws -> [_]' does not support destructuring
          >>
          >> This may not be a common pattern in most projects, but we heavily use
        this style
          >> in the Kickstarter app in our functional and FRP code. Definitely not
        the most
          >> common coding pattern, but a very expressive one that we rely on.
          >>
          >> Our interim solution is a bunch of overloaded helpers, e.g.:
          >>
          >> func tupleUp<A, B, C>(_ f: (A, B) -> C) -> ((A, B)) -> C { return }
          >>
          >> zip([1, 2, 3], [4, 5, 6]).map(tupleUp(add))
          >>
          >> Stephen
          >
          > .
          >
        _______________________________________________
        swift-evolution mailing list
        swift-evolution@swift.org <mailto:swift-evolution@swift.org>
        <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>
        https://lists.swift.org/mailman/listinfo/swift-evolution
        <https://lists.swift.org/mailman/listinfo/swift-evolution&gt;

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

Is the version you suggest to add to my list for the Swift syntax currently valid as of SE-0110 in Swift 4?

Dear all,

I made a comparison of Swift's 4 lack of tuple unsplatting, here is how it stands in
comparison with other languages

https://gist.github.com/blender/53f9568617654c38a219dd4a8353d935

Thank you! Very useful information. And also I really like the opinion of
@AliSoftware in comments for this article.

I'd suggest to add this variant to Swift section in your article:

let eighteenOrMore = ["Tom" : 33, "Rebecca" : 17, "Siri" : 5].filter {
(arg: (name: String, age: Int)) in arg.age >= 18 }

(I believe it is better that 2 others Swift variants.)

It seems for me that we need to allow some special syntax for *explicit* tuple
destructuring in closures to make all happy.

FWIW These suggestions are my favorite:

1. Just allow type inference for tuple's destructured variables in this position:

.filter { (arg: (name, age)) in arg.age >= 18 }

2. (1) + allow underscore for tuple argument name:

.filter { (_: (name, age)) in age >= 18 }

3. (2) + allow to omit parenthesis (probably only in case of just one tuple argument)

.filter { _: (name, age) in age >= 18 }

4. Use pattern matching syntax:

.filter { case let (name, age) in age >= 18 }

(looks similar as allowed today: if case let (name, age) = x { print(name, age) } )

5. Use two pairs of parenthesis :

.filter { ((name, age)) in age >= 18 }

Btw, about the 5th variant. If took what is allowed today:
.filter { (arg: (name: String, age: Int)) in arg.age >= 18 }
, and allow type inference for tuple part arguments, we'll have this:
.filter { (arg: (name, age)) in arg.age >= 18 }
, and if additionally allow skipping of tuple argument declaration we'll have:
.filter { ((name, age)) in arg.age >= 18 }
I.e. two pairs for parenthesis for tuple destructuring, and such syntax is similar to
the type this closure should have : ((String, Int)) -> Bool

···

On Thursday, June 1, 2017 9:32 PM, Vladimir.S <svabox@gmail.com> wrote:
On 01.06.2017 19:31, Tommaso Piazza wrote:

On Thursday, June 1, 2017 12:25 PM, Vladimir.S via swift-evolution > <swift-evolution@swift.org> wrote:

On 01.06.2017 0:42, John McCall wrote:
>> On May 31, 2017, at 2:02 PM, Stephen Celis <stephen.celis@gmail.com > <mailto:stephen.celis@gmail.com>> wrote:
>>> On May 28, 2017, at 7:04 PM, John McCall via swift-evolution > >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>>
>>> Yes, I agree. We need to add back tuple destructuring in closure parameter
>>> lists because this is a serious usability regression. If we're reluctant to
>>> just "do the right thing" to handle the ambiguity of (a,b), we should at least
>>> allow it via unambiguous syntax like ((a,b)). I do think that we should just
>>> "do the right thing", however, with my biggest concern being whether there's
>>> any reasonable way to achieve that in 4.0.
>>
>> Closure parameter lists are unfortunately only half of the equation here. This
>> change also regresses the usability of point-free expression.
>
> The consequences for point-free style were expected and cannot really be
> eliminated without substantially weakening SE-0110. Closure convenience seems to
> me to be a much more serious regression.

John, do you also want to say "and without weakening SE-0066"? Because, if I
understand correctly, in this case:

func add\(\_ x: Int, \_ y: Int\) \-&gt; Int \{
  return x \+ y
\}

zip\(\[1, 2, 3\], \[4, 5, 6\]\)\.map\(add\)

.. we have a clear function type mismatch situation, when map() expects function of
type ((Int, Int))->Int, but function of type (Int,Int)->Int is provided ? So probably
the additional 'reason' of the 'problem' in this case is SE-0066, no?
Or I don't understand the SE-0066 correctly..
Do we want to allow implicit conversions between function type ((Int,Int))->Int and
(Int,Int)->Int?

Quote from SE-0066:
---
(Int, Int) -> Int // function from Int and Int to Int
((Int, Int)) -> Int // function from tuple (Int, Int) to Int
---

During this discussion I see a wish of some group of developers to just return back
tuple splatting for function/closure arguments, so they can freely send tuple to
function/closure accepting a list of parameters(and probably vise-versa).
Is it worth to follow SE-0066 and SE-0110 as is, i.e. disallow tuple deconstructing
and then, as additive change improve the situation with tuple
splatting/deconstructing later with separate big proposal?

Btw, about the SE-0110 proposal. It was discussed, formally reviewed and accepted. I
expect that its revision also should be formally proposed/reviewed/accepted to
collect a wide range of opinions and thoughts, and attract the attention of
developers in this list to the subject.

Also, if we revisit SE-0110, will this code be allowed?:

func foo(_ callback: ((Int,Int))->Void) {}
let mycallback = {(x:Int, y:Int)->Void in }
foo(mycallback)

and

func foo(_ callback: (Int,Int)->Void) {}
let mycallback = {(x: (Int, Int))->Void in }
foo(mycallback)

If so, what will be result of this for both cases? :

print(type(of:mycallback)) // (Int,Int)->Void or ((Int,Int))->Void

If allowed, do we want to allow implicit conversion between types (Int,Int)->Void and
((Int,Int))->Void in both directions? (Hello tuple splatting?)

>
> John.
>
>
>>
>> func add(_ x: Int, _ y: Int) -> Int { return x + y }
>>
>> zip([1, 2, 3], [4, 5, 6]).map(add)
>>
>> // error: nested tuple parameter '(Int, Int)' of function '(((_.Element,
>> _.Element)) throws -> _) throws -> [_]' does not support destructuring
>>
>> This may not be a common pattern in most projects, but we heavily use this style
>> in the Kickstarter app in our functional and FRP code. Definitely not the most
>> common coding pattern, but a very expressive one that we rely on.
>>
>> Our interim solution is a bunch of overloaded helpers, e.g.:
>>
>> func tupleUp<A, B, C>(_ f: (A, B) -> C) -> ((A, B)) -> C { return }
>>
>> zip([1, 2, 3], [4, 5, 6]).map(tupleUp(add))
>>
>> Stephen
>
> .
>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution