Proposal: Always flatten the single element tuple

That's wrong.

"I and others" have always been after ergonomics regressions, not "reversing 5 or 6 other proposals" (how dare you???)

Gw

···

Le 7 juin 2017 à 15:28, Xiaodi Wu <xiaodi.wu@gmail.com> a écrit :

These *are* changes related to SE-0110, and Chris and others have already proposed possibilities for sugar that could make this better. That’s exactly the discussion that was originally started, to be distinguished from the alternative in which you and others are proposing reversing 5 or 6 other proposals.

Let’s clarify: you just wrote that you have problems with SE-0029, SE-0066, SE-0110, and “before,” did you not?

WTF? No I did not (citation below)!

···

Le 7 juin 2017 à 15:52, Xiaodi Wu <xiaodi.wu@gmail.com> a écrit :

Le 7 juin 2017 à 15:02, Gwendal Roué <gwendal.roue@gmail.com> a écrit :

Le 7 juin 2017 à 14:42, Vladimir.S <svabox@gmail.com <mailto:svabox@gmail.com>> a écrit :

Gwendal, again, you are proposing to revert not just SE-0110 and SE-0066 but mainly SE-0029 "Remove implicit tuple splat behavior from function applications"
(https://github.com/apple/swift-evolution/blob/master/proposals/0029-remove-implicit-tuple-splat.md\)

Do you mean that the regressions Stephen and I have shown have been introduced not only by SE-0110, but before SE-0066, and SE-0029?

Your attitude is obnoxious. Enough Swift evolution for me today.

Gwendal

While SE-0025 was generally regarded as unfortunate, the thousands of emails that followed relitigating it were much, much worse.

The removal of implicit tuple splatting, which is *not* SE-0110, was approved on the understanding that it would be a regression until explicit tuple splatting is introduced. This tradeoff was considered and approved. It’s clear that you disagree, but that is not grounds to divert a necessary discussion on mitigating SE-0110 into relitigating something else.

Push me out if you want, but will you push out those blatant wounds out as well?

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

Why not
columns.index { (arg: (column: String, _: Int)) in arg.column.lowercased() == lowercaseName }
?

Yes, I understand that first syntax short and not verbose, but the alternative you provided IMHO much worse than explicit type declaration in closure.

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

Can't compile something like this even in Swift 3, could you provide a small code snippet for this?

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

Same, why not

.map { (arg: (table: String, columns: [String])) in "\(arg.table)(\(arg.columns.sorted().joined(separator: ", ")))" }

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

Same.

See also messages from Stephen Cellis, who shows how other kinds of developer code has lost expressivity and clarity with those changes that have been "considered and approved".

Gwendal, no one saying that new syntax is better, that it is good thing that we lost the short syntax for tuple argumment deconstructions in closures.

But there is just no easy/obvious way to keep that syntax in Swift 4. The problem can't be solved just by not implementing SE-0110, as in Swift4 we should have two separate function types: one that takes single tuple argument and second that accepts a list of arguments, i.e. (Int,Int)->() and ((Int,Int))->() should be two different types now.

This is not just SE-0110, this is also SE-0066, so, to be correct, you should propose to revisit it also.

Please look here:

func foo(_ x: Int, _ y: Int) {} // type(of: foo) should be (Int, Int)->()
func bar(_ x (Int, Int)) {} // type(of: bar) should be ((Int, Int))->()

The above is described in SE-0066. Then, you have a closure constants:

var fooClosure = {(x: Int, y: Int) in }
var barClosure = {(x: (Int, Int)) in }

what should be types of these closures? Obvious the same: (Int,Int)->() and ((Int,Int))->() respectively.

Then you have a func that accepts ((Int,Int))->Int closure:

func schedule(callback: ((Int,Int))->()) {..}

, given type of foo func is (Int, Int)->() , do you suggest to allow sending foo to 'schedule' func? The same question is for fooClosure

schedule(callback: foo) // ??
schedule(callback: fooClosure) // ??

Probably we can(if technically possible, I don't know) to always allow sending of function/closure with list of arguments when function with one tuple is required. I don't know how such exceptional rule would looks like inside type system of Swift, what should be result of 'foo is ((Int,Int))->()' then and 'type(of:foo) == type(of:bar)' in such case.
But this requires a formal proposal, review period and implementation(as I understand, better before Swift 4 release). Probably you can submit such proposal, go through the review period and help with implementation.
In this case we'll have the same user-friendly closure/function parameters expirience but with respect to correct function types.

But currently we have a situation: argument of type ((Int,Int))->() is required, and we provide argument of another type : (Int,Int)->() i.e. incorrect type.
The only obvious solution here is using the common rule for type mismatch - disallow this.

Currently we have a number of suggestions how we can improve usability for the discussed problem:

* use 'let' syntax in closure argument list to deconstruct tuple argument

* use doubled parenthesis to deconstruct tuple argument: { ((key, value)) in .. }

* generation of closure of correct type if closure is declared inside function call and arguments have no type annotations, i.e.
   //schedule(callback: foo) // disallowed, type mismatch
   //schedule(callback: fooClosure) // disallowed, type mismatch

   // allowed. compiler will generate closure of type ((Int,Int))->() from this code
   schedule { x,y in }

   // type mismatch, this syntax defines closure of (Int,Int)->() type
   //schedule { (x: Int, y: Int) in }

But because all of this are additional features that can be added later, and each required to be reviewed/discussed in details, core team can decide to delay such 'fix' for after-release period. Let's wait and see what core team had to say about this subject.

···

On 07.06.2017 16:20, Gwendal Roué wrote:

Le 7 juin 2017 à 15:11, Xiaodi Wu <xiaodi.wu@gmail.com >> <mailto:xiaodi.wu@gmail.com>> a écrit :

Cheers,
Gwendal

Yes, we are discussing the *potential* solutions for Swift 4 that can decrease the pain of migration of *some* Swift3 code, given (Int,Int)->() and ((Int,Int))->() are different types in Swift 4.

But, as was said by Mark Lacey in this thread later, there is an "overload" problem for such solution, when closure of kind {x, y in ..} can be sent to overloaded func like here:

func overloaded(_ fn: (Int, Int) -> Int) { fn(1,2) }
func overloaded(_ fn: ((Int, Int)) -> Int) { fn((3,4)) }

overloaded { x, y in x + y }
overloaded { (x, y) in x + y }

Compiler is not able to determinate which type of closure do you want in each case. For ((Int,Int))->Int you still need some 'specific' syntax which disambiguate the call. So we *still* need some good syntax to destructure tuple argument in closure to use in place of ((Int,Int))->Int closure.

This means that there is no sense to allow very 'magic' {x,y in ..} syntax and compiler-detected&generated type of closure if we still need good syntax for destructuring tuple argument in ((Int,Int))->().

Yes, Mark was very right asking about overloads. But is it a problem that does not have a solution which preserves ergonomics?

    func notOverloaded1(_ closure: (Int, Int) -> Int) -> String { return "notOverloaded1" }
    func notOverloaded2(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "notOverloaded3" }
    
    func overloaded(_ closure: (Int, Int) -> Int) -> String { return "overloaded 1" }
    func overloaded(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "overloaded 2" }
    
    // not overloaded => not ambiguous
    notOverloaded1 { x, y in x + y }
    notOverloaded1 { (x, y) in x + y }
    notOverloaded1 { _ in 1 }
    notOverloaded2 { x, y in x + y }
    notOverloaded2 { (x, y) in x + y }
    notOverloaded2 { _ in 1 }
    
    // overloaded => resolve ambiguity on closure argument, when possible
    overloaded { x, y in x + y } // "overloaded 1"
    overloaded { (x, y) in x + y } // "overloaded 1"
    overloaded { t in t.lhs + t.rhs } // "overloaded 2"
    overloaded { (t) in t.lhs + t.rhs } // "overloaded 2”

This is exactly what happens today as a result of SE-0110 since the first two calls take two arguments, and the next two take a single argument.

    overloaded { _ in 1 } // error: ambiguous use of ‘overloaded'

With SE-0110 in its current form, this calls the tuple version since there is a single closure parameter listed.

    overloaded { (_) in 1 } // "overloaded 1”

With SE-0110 in its current form, this calls the tuple version since there is a single closure parameter listed. I don’t understand why you would suggest this should call the two-argument version since that’s inconsistent with the other closures above that have a single argument.

    overloaded { (_, _) in 1 } // "overloaded 2”

With SE-0110 in its current form, this calls the two-argument version since there are two listed arguments.

Mark

···

On Jun 8, 2017, at 11:37 PM, Gwendal Roué via swift-evolution <swift-evolution@swift.org> wrote:

Le 9 juin 2017 à 07:56, Vladimir.S via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

See the error on `overloaded { _ in 1 }`, because _ means "I don't care". Well, here you have to care because of overloading. Ambiguity is resolved with parenthesis. This is a specific behavior for `_`.

Gwendal

-------

PS, I had a little look at how Swift 3 currently behave with overloading ? Badly, actually:

    // SWIFT 3
    
    func f(_ closure: (Int, Int) -> Int) -> String { return "two arguments" }
    // error: invalid redeclaration of 'f'
    // func f(_ closure: ((Int, Int)) -> Int) -> String { return "one anonymous tuple argument" }
    func f(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "one named tuple argument" }
    
    // error: ambiguous use of 'f'
    // f { x, y in x + y }
    
    f { t in t.rhs + t.lhs } // "one named tuple argument"
    
    // error: ambiguous use of 'f'
    // f { t in t.0 + t.1 } // "one named tuple argument"
    
    // error: ambiguous use of 'f'
    let c = { (a: Int, b: Int) -> Int in a + b }
    type(of: c) // ((Int, Int) -> Int).Type
    // error: ambiguous use of 'f'
    // f(c)

Swift 3 does not allow overloading ((Int, Int)) -> Int and (Int, Int) -> Int, but allows overloading ((lhs: Int, rhs: Int)) -> Int and (Int, Int) -> Int.

Yet I could never call the (Int, Int) -> Int version, even when I provide with a function that exactly matches its signature. And there are much too many ambiguous situations the compiler can't deal with.

So yes, there is a problem with Swift 3.

How is it with Swift 4 (2017-06-02 snapshot)?

    // SWIFT 4
    
    func f(_ closure: (Int, Int) -> Int) -> String { return "two arguments" }
    // error: invalid redeclaration of 'f'
    // func f(_ closure: ((Int, Int)) -> Int) -> String { return "one anonymous tuple argument" }
    func f(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "one named tuple argument" }

    f { x, y in x + y } // "two arguments"
    f { t in t.rhs + t.lhs } // "one named tuple argument"
    f { t in t.0 + t.1 } // "one named tuple argument"
    
    let c = { (a: Int, b: Int) -> Int in a + b }
    type(of: c) // ((Int, Int) -> Int).Type
    f(c) // "two arguments"

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

Yes, Mark was very right asking about overloads. But is it a problem that does not have a solution which preserves ergonomics?

    func notOverloaded1(_ closure: (Int, Int) -> Int) -> String { return "notOverloaded1" }
    func notOverloaded2(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "notOverloaded3" }
    
    func overloaded(_ closure: (Int, Int) -> Int) -> String { return "overloaded 1" }
    func overloaded(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "overloaded 2" }
    
    // not overloaded => not ambiguous
    notOverloaded1 { x, y in x + y }
    notOverloaded1 { (x, y) in x + y }
    notOverloaded1 { _ in 1 }
    notOverloaded2 { x, y in x + y }
    notOverloaded2 { (x, y) in x + y }
    notOverloaded2 { _ in 1 }
    
    // overloaded => resolve ambiguity on closure argument, when possible
    overloaded { x, y in x + y } // "overloaded 1"
    overloaded { (x, y) in x + y } // "overloaded 1"
    overloaded { t in t.lhs + t.rhs } // "overloaded 2"
    overloaded { (t) in t.lhs + t.rhs } // "overloaded 2"
    overloaded { _ in 1 } // error: ambiguous use of 'overloaded'
    overloaded { (_) in 1 } // "overloaded 1"
    overloaded { (_, _) in 1 } // "overloaded 2"

See the error on `overloaded { _ in 1 }`, because _ means "I don't care". Well, here you have to care because of overloading. Ambiguity is resolved with parenthesis. This is a specific behavior for `_`.

Gwendal

···

Le 9 juin 2017 à 07:56, Vladimir.S via swift-evolution <swift-evolution@swift.org> a écrit :

Yes, we are discussing the *potential* solutions for Swift 4 that can decrease the pain of migration of *some* Swift3 code, given (Int,Int)->() and ((Int,Int))->() are different types in Swift 4.

But, as was said by Mark Lacey in this thread later, there is an "overload" problem for such solution, when closure of kind {x, y in ..} can be sent to overloaded func like here:

func overloaded(_ fn: (Int, Int) -> Int) { fn(1,2) }
func overloaded(_ fn: ((Int, Int)) -> Int) { fn((3,4)) }

overloaded { x, y in x + y }
overloaded { (x, y) in x + y }

Compiler is not able to determinate which type of closure do you want in each case. For ((Int,Int))->Int you still need some 'specific' syntax which disambiguate the call. So we *still* need some good syntax to destructure tuple argument in closure to use in place of ((Int,Int))->Int closure.

This means that there is no sense to allow very 'magic' {x,y in ..} syntax and compiler-detected&generated type of closure if we still need good syntax for destructuring tuple argument in ((Int,Int))->().

-------

PS, I had a little look at how Swift 3 currently behave with overloading ? Badly, actually:

    // SWIFT 3
    
    func f(_ closure: (Int, Int) -> Int) -> String { return "two arguments" }
    // error: invalid redeclaration of 'f'
    // func f(_ closure: ((Int, Int)) -> Int) -> String { return "one anonymous tuple argument" }
    func f(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "one named tuple argument" }
    
    // error: ambiguous use of 'f'
    // f { x, y in x + y }
    
    f { t in t.rhs + t.lhs } // "one named tuple argument"
    
    // error: ambiguous use of 'f'
    // f { t in t.0 + t.1 } // "one named tuple argument"
    
    // error: ambiguous use of 'f'
    let c = { (a: Int, b: Int) -> Int in a + b }
    type(of: c) // ((Int, Int) -> Int).Type
    // error: ambiguous use of 'f'
    // f(c)

Swift 3 does not allow overloading ((Int, Int)) -> Int and (Int, Int) -> Int, but allows overloading ((lhs: Int, rhs: Int)) -> Int and (Int, Int) -> Int.

Yet I could never call the (Int, Int) -> Int version, even when I provide with a function that exactly matches its signature. And there are much too many ambiguous situations the compiler can't deal with.

So yes, there is a problem with Swift 3.

How is it with Swift 4 (2017-06-02 snapshot)?

    // SWIFT 4
    
    func f(_ closure: (Int, Int) -> Int) -> String { return "two arguments" }
    // error: invalid redeclaration of 'f'
    // func f(_ closure: ((Int, Int)) -> Int) -> String { return "one anonymous tuple argument" }
    func f(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "one named tuple argument" }

    f { x, y in x + y } // "two arguments"
    f { t in t.rhs + t.lhs } // "one named tuple argument"
    f { t in t.0 + t.1 } // "one named tuple argument"
    
    let c = { (a: Int, b: Int) -> Int in a + b }
    type(of: c) // ((Int, Int) -> Int).Type
    f(c) // "two arguments"

Much better.

Vladimir.S via swift-evolution <swift-evolution@swift.org> 於 2017年6月9日 上午5:05 寫道:

Well please no:

>let fn2: ((Int, Int)) -> Void = { lhs, rhs in }|

Instead use destructuring sugar pitched by Chris Lattner on the other thread:

>let fn2: ((Int, Int)) -> Void = { ((lhs, rhs)) in }|

I think this suggestion is better than the status quo. I'm wondering, though, if we should just drop the outer set of parentheses entirely, unless you're also putting types on the parameters. That is, a closure of type `(Int, Int) -> T` can look like this:

{ (x: Int, y: Int) in … }

Or it can look like this:

{ x, y in … }

But it *cannot* look like this:

{ (x, y) in … }

The `(x, y)` form can instead be a closure of a type like `((Int, Int)) -> T`, which immediately destructures the tuple parameter into separate constants.

--
Brent Royal-Gordon
Architechies

Hello,
There's a difference, in the mind of people here that try to show how bad were the recent changes, between:
1: closures defined independently
2: closures given as a parameter to a function.
I think that we all agree that the type of a closure that is defined independently should be well defined:
// Choose between (Int, Int) -> () or ((x: Int, y: Int)) -> ()
leta = { (x: Int, y: Int) -> Intin... }
letb = { ((x: Int, y: Int)) -> Intin... }
However, when a closure is given as an argument of a function that expects a closure, we ask for the maximum possible flexibility, as Swift 3 did:
funcwantsTwoArguments(_closure: (Int, Int) -> Int) { closure(1, 2) }
wantsTwoArguments{ a, b ina + b }
wantsTwoArguments{ (a, b) ina + b }
wantsTwoArguments{ t int.0+ t.1} // OK, maybe not
funcwantsATupleArgument(_closure: ((Int, Int)) -> Int) { closure((1, 2)) }
wantsATupleArgument{ a, b ina + b }
wantsATupleArgument{ (a, b) ina + b }
wantsATupleArgument{ t int.0+ t.1}
funcwantsANamedTupleArgument(_closure: ((lhs: Int, rhs: Int)) -> Int) { closure((lhs: 1, rhs: 2)) }
wantsANamedTupleArgument{ a, b ina + b }
wantsANamedTupleArgument{ (a, b) ina + b }
wantsANamedTupleArgument{ t int.lhs + t.rhs }

It's nice to see that we are agreed that func/closures declared separately should have clearly defined type and (at least for now) can't be interchangeable.

And personally I agree that this could be a solution to migration problem, compiler can generate closure of correct(requested) type if such closure:
1. declared inplace of func call as parameter of that func
2. has no type annotations for its arguments
3. (probably, can discuss) has no parenthesis for its arguments, because one pair of parenthesis in argument list declares closure of type (list of arguments)->T in other situations.

So, we can have

wantsTwoArguments{ a, b in a + b } // (Int,Int)->() will be generated
wantsTwoArguments{ (a, b) in a + b } // syntax of (Int,Int)->() closure

wantsATupleArgument{ a, b in a + b } // ((Int,Int))->() will be generated
wantsATupleArgument{ t in t.0+ t.1 } // syntax of ((Int,Int))->() closure

wantsANamedTupleArgument{ a, b in a + b } // ((Int,Int))->() will be generated
wantsANamedTupleArgument{ t in t.lhs + t.rhs } // syntax of ((Int,Int))->() closure

That's what Swift 3 has done now, but forbidden by Swift 4. Isn't it?

Yes, we are discussing the *potential* solutions for Swift 4 that can decrease the pain of migration of *some* Swift3 code, given (Int,Int)->() and ((Int,Int))->() are different types in Swift 4.

But, as was said by Mark Lacey in this thread later, there is an "overload" problem for such solution, when closure of kind {x, y in ..} can be sent to overloaded func like here:

func overloaded(_ fn: (Int, Int) -> Int) { fn(1,2) }
func overloaded(_ fn: ((Int, Int)) -> Int) { fn((3,4)) }

overloaded { x, y in x + y }
overloaded { (x, y) in x + y }

Compiler is not able to determinate which type of closure do you want in each case. For ((Int,Int))->Int you still need some 'specific' syntax which disambiguate the call. So we *still* need some good syntax to destructure tuple argument in closure to use in place of ((Int,Int))->Int closure.

This means that there is no sense to allow very 'magic' {x,y in ..} syntax and compiler-detected&generated type of closure if we still need good syntax for destructuring tuple argument in ((Int,Int))->().

···

On 09.06.2017 4:02, susan.doggie@gmail.com wrote:

On 08.06.2017 21:17, Gwendal Roué via swift-evolution wrote:

Le 8 juin 2017 à 19:40, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

On Jun 7, 2017, at 3:03 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

So, { a,b in ...} will be a special syntax for closure which type will be defined by compiler by type of func's parameter.

The question is if community and core team will support this idea, if that idea is better than other ideas like {((a,b)) in ..} for tuple deconstruction, and if this could be implemented before Swift 4 release.

*This gives us the ability to deal with unfitted function signatures.* For example, most Dictionary methods. Yes, they are usually unfitted:
extensionDictionary{
funcforEach(_body: ((key: Key, value: Value)) throws-> Void) rethrows
     }
Who cares about this named (key:value:) tuple? Absolutely nobody, as exemplified by this remarquable Swift 3 snippet below, where no tuple, no `key`, and no `value` is in sight:
letscores: [String: Int] = ... // [playerName: score]
     scores.forEach { name, score in
print("\(name): \(score)")
     }
Do you see?

    let scores: [String: Int] = ["a":1, "b":2]

    scores.forEach { (score: (name:String, value:Int)) in
        print("\(score.name): \(score.value)")
    }

I'm not saying that this syntax as good as in your example, but it is not as bad/ugly as you say.

Gwendal
_______________________________________________
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

    func notOverloaded1(_ closure: (Int, Int) -> Int) -> String { return "notOverloaded1" }
    func notOverloaded2(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "notOverloaded3" }
    
    func overloaded(_ closure: (Int, Int) -> Int) -> String { return "overloaded 1" }
    func overloaded(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "overloaded 2" }
    
    // not overloaded => not ambiguous
    notOverloaded1 { x, y in x + y }
    notOverloaded1 { (x, y) in x + y }
    notOverloaded1 { _ in 1 }
    notOverloaded2 { x, y in x + y }
    notOverloaded2 { (x, y) in x + y }
    notOverloaded2 { _ in 1 }
    
    // overloaded => resolve ambiguity on closure argument, when possible
    overloaded { x, y in x + y } // "overloaded 1"
    overloaded { (x, y) in x + y } // "overloaded 1"
    overloaded { t in t.lhs + t.rhs } // "overloaded 2"
    overloaded { (t) in t.lhs + t.rhs } // "overloaded 2”

This is exactly what happens today as a result of SE-0110 since the first two calls take two arguments, and the next two take a single argument.

No, as a playground quickly reveals:

    func notOverloaded1(_ closure: (Int, Int) -> Int) -> String { return "notOverloaded1" }
    func notOverloaded2(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "notOverloaded3" }
    
    // not overloaded => not ambiguous
    notOverloaded1 { x, y in x + y }
    notOverloaded1 { (x, y) in x + y }
    notOverloaded1 { _ in 1 } // Swift 4 error
    notOverloaded2 { x, y in x + y } // Swift 4 error
    notOverloaded2 { (x, y) in x + y } // Swift 4 error
    notOverloaded2 { _ in 1 }

Those errors are the famous regressions we want to fix.

    overloaded { _ in 1 } // error: ambiguous use of ‘overloaded'

With SE-0110 in its current form, this calls the tuple version since there is a single closure parameter listed.

It would be nicer if. { _ in ... } would always mean "I don't care". With an ambiguity in the sample code we're looking at, and a compiler error.

The { (_, _) in ... } form is arguably cluttered, and it would be nicer it is was required only for disambiguition.

    overloaded { (_) in 1 } // "overloaded 1”

With SE-0110 in its current form, this calls the tuple version since there is a single closure parameter listed. I don’t understand why you would suggest this should call the two-argument version since that’s inconsistent with the other closures above that have a single argument.

    overloaded { (_, _) in 1 } // "overloaded 2”

With SE-0110 in its current form, this calls the two-argument version since there are two listed arguments.

I was wrong, sorry. I meant what you replied.

Gwendal

( /* two parameters, not a tuple */ (Int, Int) -> Void).self == (( /* one tuple parameter */ (Int, Int)) -> Void).self // BUG, which SE-0110 should have fixed, but still didn't

Plus inlined:

···

--
Adrian Zubarev
Sent with Airmail

Am 7. Juni 2017 um 13:53:08, Gwendal Roué (gwendal.roue@gmail.com) schrieb:

func sum1\(\_ lhs: Int, \_ rhs: Int\) \-&gt; Int \{ return lhs \+ rhs \}
func sum2\(lhs: Int, rhs: Int\) \-&gt; Int \{ return lhs \+ rhs \}
func sum3\(tuple: \(Int, Int\)\) \-&gt; Int \{ return tuple\.0 \+ tuple\.1 \}
func sum4\(tuple: \(lhs: Int, rhs: Int\)\) \-&gt; Int \{ return tuple\.lhs \+ tuple\.rhs \}

// two arguments
func f1\(\_ closure: \(Int, Int\) \-&gt; Int\) \{ closure\(1, 2\) \}

Bug abuse:

f1 \{ tuple in tuple\.0 \+ tuple\.1 \}
f1 \{ \(tuple\) in tuple\.0 \+ tuple\.1 \}
f1\(sum3\)
f1\(sum4\)

`f2` is no different from `f1`.

Bugs, which should be fixed by `((lhs, rhs)) in`:

// one tuple argument
func f3\(\_ closure: \(\(Int, Int\)\) \-&gt; Int\) \{ closure\(\(1, 2\)\) \}
f3 \{ lhs, rhs in lhs \+ rhs \}
f3 \{ \(lhs, rhs\) in lhs \+ rhs \}

Not possible because ultimately the closures have different types.

f3\(\+\)
f3\(sum1\)
f3\(sum2\)    

Bugs, which should be fixed by `((lhs, rhs)) in`:

// one keyed tuple argument
func f4\(\_ closure: \(\(a: Int, b: Int\)\) \-&gt; Int\) \{ closure\(\(a: 1, b: 2\)\) \}
f4 \{ lhs, rhs in lhs \+ rhs \}
f4 \{ \(lhs, rhs\) in lhs \+ rhs \}

Not possible because ultimately the closures have different types.

f4\(\+\)
f4\(sum1\)
f4\(sum2\)

Not sure, but I assume this shouldn’t be possible because tuple labels are part of the type and it’s a mismatch like `(lhs: Int, rhs: Int).Type != (a: Int, b: Int).Type`, therefore closures should be of different types as well.

f4\(sum4\)

Please refrain from personal attacks. Are we reading the same sentence? I
see that you literally listed three proposals (and “before”) and blamed
them for “regressions” that you want to reverse. If that’s not your
meaning, what do you mean?

I asked you in my first reply to you, is your view that the distinction
itself between (Int, Int) -> Int and ((Int, Int)) -> Int is problematic? If
so, this is a very different discussion from seeking solutions to mitigate
particular ergonomic issues that arise from the change. However, you have
not answered the question.

···

On Wed, Jun 7, 2017 at 10:03 Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 7 juin 2017 à 15:52, Xiaodi Wu <xiaodi.wu@gmail.com> a écrit :

Let’s clarify: you just wrote that you have problems with SE-0029,
SE-0066, SE-0110, and “before,” did you not?

WTF? No I did not (citation below)!

Le 7 juin 2017 à 15:02, Gwendal Roué <gwendal.roue@gmail.com> a écrit :

Le 7 juin 2017 à 14:42, Vladimir.S <svabox@gmail.com> a écrit :

Gwendal, again, you are proposing to revert not just SE-0110 and SE-0066
but mainly SE-0029 "Remove implicit tuple splat behavior from function
applications"
(
https://github.com/apple/swift-evolution/blob/master/proposals/0029-remove-implicit-tuple-splat.md
)

Do you mean that the regressions Stephen and I have shown have been
introduced not only by SE-0110, but before SE-0066, and SE-0029?

Your attitude is obnoxious. Enough Swift evolution for me today.

While SE-0025 was generally regarded as unfortunate, the thousands of emails that followed relitigating it were much, much worse.

The removal of implicit tuple splatting, which is *not* SE-0110, was approved on the understanding that it would be a regression until explicit tuple splatting is introduced. This tradeoff was considered and approved. It’s clear that you disagree, but that is not grounds to divert a necessary discussion on mitigating SE-0110 into relitigating something else.

Push me out if you want, but will you push out those blatant wounds out as well?
Example 1
- return columns.index { (column, _) in column.lowercased() == lowercaseName }
+ return columns.index { $0.0.lowercased() == lowercaseName }

Why not
columns.index { (arg: (column: String, _: Int)) in arg.column.lowercased() == lowercaseName }
?

It would works, but it's less than ideal: the `args` name has no meaning. We have an extra type declaration instead of type inference. It's much longer. The clarity is tremendously reduced.

- return columns.index { (column, _) in column.lowercased() == lowercaseName }
+ return columns.index { (arg: (column: String, _)) in arg.column.lowercased() == lowercaseName }

Yes, I understand that first syntax short and not verbose, but the alternative you provided IMHO much worse than explicit type declaration in closure.

It's not an "alternative": it's Swift 3.

Maybe you did already profit from it, but did not notice.

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

Can't compile something like this even in Swift 3, could you provide a small code snippet for this?

Sure:

    let mapping: [String: String] = ...
    mapping.map { (mappedColumn, baseColumn) -> (Int, String) in ... }

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

Same, why not

.map { (arg: (table: String, columns: [String])) in "\(arg.table)(\(arg.columns.sorted().joined(separator: ", ")))" }

Same answer: the extra `args` works, but we still have an ergonomics regression from Swift 3.

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

Same.

Indeed.

See also messages from Stephen Cellis, who shows how other kinds of developer code has lost expressivity and clarity with those changes that have been "considered and approved".

Gwendal, no one saying that new syntax is better, that it is good thing that we lost the short syntax for tuple argumment deconstructions in closures.

Good to hear :-)

But there is just no easy/obvious way to keep that syntax in Swift 4. The problem can't be solved just by not implementing SE-0110, as in Swift4 we should have two separate function types: one that takes single tuple argument and second that accepts a list of arguments, i.e. (Int,Int)->() and ((Int,Int))->() should be two different types now.

Of course I understand that.

I expect the compiler to perform the expected conversions for ergonomics' sake.

I can understand that this sugar could be added *after* current problems which are not related to ergonomics are solved. I just want to make it clear that there is an ergonomics problem *now*, and that solving it should have a very high priority.

This is not just SE-0110, this is also SE-0066, so, to be correct, you should propose to revisit it also.

Please look here:

func foo(_ x: Int, _ y: Int) {} // type(of: foo) should be (Int, Int)->()
func bar(_ x (Int, Int)) {} // type(of: bar) should be ((Int, Int))->()

The above is described in SE-0066. Then, you have a closure constants:

var fooClosure = {(x: Int, y: Int) in }
var barClosure = {(x: (Int, Int)) in }

what should be types of these closures? Obvious the same: (Int,Int)->() and ((Int,Int))->() respectively.

I guess you mean, not the same: (Int, Int) -> () vs. ((Int, Int)) -> ().

Then you have a func that accepts ((Int,Int))->Int closure:

func schedule(callback: ((Int,Int))->()) {..}

, given type of foo func is (Int, Int)->() , do you suggest to allow sending foo to 'schedule' func? The same question is for fooClosure

schedule(callback: foo) // ??
schedule(callback: fooClosure) // ??

Yes, I do. For ergonomics' sake.

Probably we can(if technically possible, I don't know) to always allow sending of function/closure with list of arguments when function with one tuple is required. I don't know how such exceptional rule would looks like inside type system of Swift, what should be result of 'foo is ((Int,Int))->()' then and 'type(of:foo) == type(of:bar)' in such case.
But this requires a formal proposal, review period and implementation(as I understand, better before Swift 4 release). Probably you can submit such proposal, go through the review period and help with implementation.

Seriously I don't know if I have the skills to write such a proposal. It dives much too deep in the compiler internals for me to be efficient in the proposal process.

I thus hope that I did succeed showing how seriously bad are the regressions induced by SE-0110, and that a skilled language designer will sponsor an ergonomics-rescue proposal.

So far, the answer to the ergonomics regression reports have been much too often negative. I wish ergonomics had better support in the community. Very few regular developers talk here, unfortunately.

In this case we'll have the same user-friendly closure/function parameters expirience but with respect to correct function types.

That would be just great!

But currently we have a situation: argument of type ((Int,Int))->() is required, and we provide argument of another type : (Int,Int)->() i.e. incorrect type.
The only obvious solution here is using the common rule for type mismatch - disallow this.

And the obvious and easy way leads to regressions.

Currently we have a number of suggestions how we can improve usability for the discussed problem:

* use 'let' syntax in closure argument list to deconstruct tuple argument

* use doubled parenthesis to deconstruct tuple argument: { ((key, value)) in .. }

Both 'let' and doubled parenthesis fail in ergonomics.

Let me explain you why: when you provide a closure to a function, do you know if the function expects a single-tuple input, or a multiple-arguments input? You don't know.

For example, take those three functions:

  func f(_ closure:(Int, Int) -> ())
  func g(_ closure:((Int, Int)) -> ())
  func h(_ closure:((a: Int, b: Int)) -> ())

If one can always write (as in Swift 3):

  f { (a, b) in ... }
  g { (a, b) in ... }
  c { (a, b) in ... }

Then one can easily deal with a badly fit closure signature.

This is most examplified by dictionaries. They always expose (key: Key, value: Value) tuples (their Element type). Problem is that 'key' and 'value' are identifiers that only matter for dictionaries, not for dictionary users. It's very important for dictionary users to forget about tuples, and the `key` and `value` words:

  // No pollution
  dictionary.map { (name, score) in ... }

That wish (let one easily deal with a badly fit closure signature) is also asked by Stephen Cellis in his functional explorations.

* generation of closure of correct type if closure is declared inside function call and arguments have no type annotations, i.e.
//schedule(callback: foo) // disallowed, type mismatch
//schedule(callback: fooClosure) // disallowed, type mismatch

// allowed. compiler will generate closure of type ((Int,Int))->() from this code
schedule { x,y in }

// type mismatch, this syntax defines closure of (Int,Int)->() type
//schedule { (x: Int, y: Int) in }

But because all of this are additional features that can be added later, and each required to be reviewed/discussed in details, core team can decide to delay such 'fix' for after-release period. Let's wait and see what core team had to say about this subject.

I can't wait to hear from the core team.

Gwendal

···

Le 7 juin 2017 à 17:15, Vladimir.S <svabox@gmail.com> a écrit :
On 07.06.2017 16:20, Gwendal Roué wrote:

Le 7 juin 2017 à 15:11, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> a écrit :

    func notOverloaded1(_ closure: (Int, Int) -> Int) -> String { return "notOverloaded1" }
    func notOverloaded2(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "notOverloaded3" }
    
    func overloaded(_ closure: (Int, Int) -> Int) -> String { return "overloaded 1" }
    func overloaded(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "overloaded 2" }
    
    // not overloaded => not ambiguous
    notOverloaded1 { x, y in x + y }
    notOverloaded1 { (x, y) in x + y }
    notOverloaded1 { _ in 1 }
    notOverloaded2 { x, y in x + y }
    notOverloaded2 { (x, y) in x + y }
    notOverloaded2 { _ in 1 }
    
    // overloaded => resolve ambiguity on closure argument, when possible
    overloaded { x, y in x + y } // "overloaded 1"
    overloaded { (x, y) in x + y } // "overloaded 1"
    overloaded { t in t.lhs + t.rhs } // "overloaded 2"
    overloaded { (t) in t.lhs + t.rhs } // "overloaded 2”

This is exactly what happens today as a result of SE-0110 since the first two calls take two arguments, and the next two take a single argument.

No, as a playground quickly reveals:

I was specifically referring to the last four statements but could have made that more clear.

    func notOverloaded1(_ closure: (Int, Int) -> Int) -> String { return "notOverloaded1" }
    func notOverloaded2(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "notOverloaded3" }
    
    // not overloaded => not ambiguous
    notOverloaded1 { x, y in x + y }
    notOverloaded1 { (x, y) in x + y }
    notOverloaded1 { _ in 1 } // Swift 4 error

This really should be an error. It’s one thing to say that { x, y in } should work as it used to in the case where a closure taking a tuple is expected, but something else entirely to say that _ should work in any case, regardless of whether a closure taking N arguments or a closure taking an N-tuple is expected for any N. I can see a desire to make _ work for any tuple, and _, _ work for a two-tuple based on context if e.g. “x, y in” also works, but not for _ working where a closure requiring N independent arguments is expected.

    notOverloaded2 { x, y in x + y } // Swift 4 error
    notOverloaded2 { (x, y) in x + y } // Swift 4 error

In these cases using the tuple labels works as well (as it does in your examples where the overloaded function is involved). I’m not arguing that it’s as good as being able to do the apparent destructuring, but just want to point out that it works. I didn’t get the sense from your email that you found that objectionable in the cases involving overloads, but perhaps you do.

    notOverloaded2 { _ in 1 }

Those errors are the famous regressions we want to fix.

    overloaded { _ in 1 } // error: ambiguous use of ‘overloaded'

With SE-0110 in its current form, this calls the tuple version since there is a single closure parameter listed.

It would be nicer if. { _ in ... } would always mean "I don't care". With an ambiguity in the sample code we're looking at, and a compiler error.

How does making something that is unambiguous today actually be ambiguous improve things?

Mark

···

On Jun 9, 2017, at 12:12 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

The { (_, _) in ... } form is arguably cluttered, and it would be nicer it is was required only for disambiguition.

    overloaded { (_) in 1 } // "overloaded 1”

With SE-0110 in its current form, this calls the tuple version since there is a single closure parameter listed. I don’t understand why you would suggest this should call the two-argument version since that’s inconsistent with the other closures above that have a single argument.

    overloaded { (_, _) in 1 } // "overloaded 2”

With SE-0110 in its current form, this calls the two-argument version since there are two listed arguments.

I was wrong, sorry. I meant what you replied.

Gwendal

    func notOverloaded1(_ closure: (Int, Int) -> Int) -> String { return "notOverloaded1" }
    func notOverloaded2(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "notOverloaded3" }
    
    func overloaded(_ closure: (Int, Int) -> Int) -> String { return "overloaded 1" }
    func overloaded(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "overloaded 2" }
    
    // not overloaded => not ambiguous
    notOverloaded1 { x, y in x + y }
    notOverloaded1 { (x, y) in x + y }
    notOverloaded1 { _ in 1 }
    notOverloaded2 { x, y in x + y }
    notOverloaded2 { (x, y) in x + y }
    notOverloaded2 { _ in 1 }
    
    // overloaded => resolve ambiguity on closure argument, when possible
    overloaded { x, y in x + y } // "overloaded 1"
    overloaded { (x, y) in x + y } // "overloaded 1"
    overloaded { t in t.lhs + t.rhs } // "overloaded 2"
    overloaded { (t) in t.lhs + t.rhs } // "overloaded 2”

This is exactly what happens today as a result of SE-0110 since the first two calls take two arguments, and the next two take a single argument.

No, as a playground quickly reveals:

I was specifically referring to the last four statements but could have made that more clear.

    func notOverloaded1(_ closure: (Int, Int) -> Int) -> String { return "notOverloaded1" }
    func notOverloaded2(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "notOverloaded3" }
    
    // not overloaded => not ambiguous
    notOverloaded1 { x, y in x + y }
    notOverloaded1 { (x, y) in x + y }
    notOverloaded1 { _ in 1 } // Swift 4 error

This really should be an error.

I argue this is a subjective statement, and my own subjective statement is that it should not to be an error. So that { _ in ... } would always mean "I don't care".

It’s one thing to say that { x, y in } should work as it used to in the case where a closure taking a tuple is expected, but something else entirely to say that _ should work in any case, regardless of whether a closure taking N arguments or a closure taking an N-tuple is expected for any N. I can see a desire to make _ work for any tuple, and _, _ work for a two-tuple based on context if e.g. “x, y in” also works, but not for _ working where a closure requiring N independent arguments is expected.

Yes. That's why it's subjective. You argue that you don't like it, not that it's impossible to do. Because you can't prove it's impossible to do. Because I have shown that it is. In a nice manner that is easy to developers.

    notOverloaded2 { x, y in x + y } // Swift 4 error
    notOverloaded2 { (x, y) in x + y } // Swift 4 error

In these cases using the tuple labels works as well (as it does in your examples where the overloaded function is involved). I’m not arguing that it’s as good as being able to do the apparent destructuring, but just want to point out that it works. I didn’t get the sense from your email that you found that objectionable in the cases involving overloads, but perhaps you do.

    notOverloaded2 { _ in 1 }

Those errors are the famous regressions we want to fix.

    overloaded { _ in 1 } // error: ambiguous use of ‘overloaded'

With SE-0110 in its current form, this calls the tuple version since there is a single closure parameter listed.

It would be nicer if. { _ in ... } would always mean "I don't care". With an ambiguity in the sample code we're looking at, and a compiler error.

How does making something that is unambiguous today actually be ambiguous improve things?

I *do* suggest a specific handling of { _ ... }. I have shown how it can be implemented in a non-ambiguous fashion. Please don't act as if I did not.

Now the specific case of { _ ... } is interesting, but I wouldn't make it as important as the other regressions.

I should make an exhaustive compilation of Swift 4 regressions, so that you opponents can see how *far* things have changed, fixing a few corner cases, and breaking tons of valid code. At least we could discuss each case in isolation.

Gwendal

···

Le 9 juin 2017 à 09:41, Mark Lacey <mark.lacey@apple.com> a écrit :

On Jun 9, 2017, at 12:12 AM, Gwendal Roué <gwendal.roue@gmail.com <mailto:gwendal.roue@gmail.com>> wrote:

While SE-0025 was generally regarded as unfortunate, the thousands of
emails that followed relitigating it were much, much worse.

The removal of implicit tuple splatting, which is *not* SE-0110, was
approved on the understanding that it would be a regression until explicit
tuple splatting is introduced. This tradeoff was considered and approved.
It’s clear that you disagree, but that is not grounds to divert a necessary
discussion on mitigating SE-0110 into relitigating something else.

Push me out if you want, but will you push out those blatant wounds out as
well?
Example 1
- return columns.index { (column, _) in column.lowercased() ==
lowercaseName }
+ return columns.index { $0.0.lowercased() == lowercaseName }

Why not
columns.index { (arg: (column: String, _: Int)) in arg.column.lowercased()
== lowercaseName }
?

It would works, but it's less than ideal: the `args` name has no meaning.
We have an extra type declaration instead of type inference. It's much
longer. The clarity is tremendously reduced.

- return columns.index { (column, _) in column.lowercased() ==
lowercaseName }
+ return columns.index { (arg: (column: String, _)) in
arg.column.lowercased() == lowercaseName }

Yes, I understand that first syntax short and not verbose, but the
alternative you provided IMHO much worse than explicit type declaration in
closure.

It's not an "alternative": it's Swift 3.

Maybe you did already profit from it, but did not notice.

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

Can't compile something like this even in Swift 3, could you provide a
small code snippet for this?

Sure:

    let mapping: [String: String] = ...
    mapping.map { (mappedColumn, baseColumn) -> (Int, String) in ... }

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

Same, why not

.map { (arg: (table: String, columns: [String])) in
"\(arg.table)(\(arg.columns.sorted().joined(separator: ", ")))" }

Same answer: the extra `args` works, but we still have an ergonomics
regression from Swift 3.

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

Same.

Indeed.

See also messages from Stephen Cellis, who shows how other kinds of
developer code has lost expressivity and clarity with those changes that
have been "considered and approved".

Gwendal, no one saying that new syntax is better, that it is good thing
that we lost the short syntax for tuple argumment deconstructions in
closures.

Good to hear :-)

But there is just no easy/obvious way to keep that syntax in Swift 4. The
problem can't be solved just by not implementing SE-0110, as in Swift4 we
should have two separate function types: one that takes single tuple
argument and second that accepts a list of arguments, i.e. (Int,Int)->()
and ((Int,Int))->() should be two different types now.

Of course I understand that.

I expect the compiler to perform the expected conversions for ergonomics'
sake.

I can understand that this sugar could be added *after* current problems
which are not related to ergonomics are solved. I just want to make it
clear that there is an ergonomics problem *now*, and that solving it should
have a very high priority.

If this was the point you wished to make, then there isn’t any
disagreement, I don’t think. We *all* can obviously agree that there is a
loss in ergonomics due to SE-0110.

This is not just SE-0110, this is also SE-0066, so, to be correct, you

should propose to revisit it also.

Please look here:

func foo(_ x: Int, _ y: Int) {} // type(of: foo) should be (Int, Int)->()
func bar(_ x (Int, Int)) {} // type(of: bar) should be ((Int, Int))->()

The above is described in SE-0066. Then, you have a closure constants:

var fooClosure = {(x: Int, y: Int) in }
var barClosure = {(x: (Int, Int)) in }

what should be types of these closures? Obvious the same: (Int,Int)->()
and ((Int,Int))->() respectively.

I guess you mean, not the same: (Int, Int) -> () vs. ((Int, Int)) -> ().

Then you have a func that accepts ((Int,Int))->Int closure:

func schedule(callback: ((Int,Int))->()) {..}

, given type of foo func is (Int, Int)->() , do you suggest to allow
sending foo to 'schedule' func? The same question is for fooClosure

schedule(callback: foo) // ??
schedule(callback: fooClosure) // ??

Yes, I do. For ergonomics' sake.

Probably we can(if technically possible, I don't know) to always allow
sending of function/closure with list of arguments when function with one
tuple is required. I don't know how such exceptional rule would looks like
inside type system of Swift, what should be result of 'foo is
((Int,Int))->()' then and 'type(of:foo) == type(of:bar)' in such case.
But this requires a formal proposal, review period and implementation(as I
understand, better before Swift 4 release). Probably you can submit such
proposal, go through the review period and help with implementation.

Seriously I don't know if I have the skills to write such a proposal. It
dives much too deep in the compiler internals for me to be efficient in the
proposal process.

I thus hope that I did succeed showing how seriously bad are the
regressions induced by SE-0110, and that a skilled language designer will
sponsor an ergonomics-rescue proposal.

So far, the answer to the ergonomics regression reports have been much too
often negative. I wish ergonomics had better support in the community. Very
few regular developers talk here, unfortunately.

As I mentioned in a reply above, re-reading SE-0029 gives a full account of
the rationale for this series of proposals. It acknowledges that tuple
splatting is useful and explicitly welcomes a complete design for future
consideration. However, it states that this is *hard*, and that the reason
for not having one is precisely what you said: no one with both the
expertise and the time has stepped forward to do it. Again, I’m not sure
we’re covering new ground here, or even disagreeing.

In this case we'll have the same user-friendly closure/function parameters

expirience but with respect to correct function types.

That would be just great!

But currently we have a situation: argument of type ((Int,Int))->() is
required, and we provide argument of another type : (Int,Int)->() i.e.
incorrect type.

The only obvious solution here is using the common rule for type mismatch
- disallow this.

And the obvious and easy way leads to regressions.

Currently we have a number of suggestions how we can improve usability for
the discussed problem:

* use 'let' syntax in closure argument list to deconstruct tuple argument

* use doubled parenthesis to deconstruct tuple argument: { ((key, value))
in .. }

Both 'let' and doubled parenthesis fail in ergonomics.

Let me explain you why: when you provide a closure to a function, do you
know if the function expects a single-tuple input, or a multiple-arguments
input? You don't know.

Wait, why don’t you know? This is a baffling statement for me. Of course
you would know, by inspection, surely?

For example, take those three functions:

···

On Wed, Jun 7, 2017 at 14:33 Gwendal Roué via swift-evolution < swift-evolution@swift.org> wrote:

Le 7 juin 2017 à 17:15, Vladimir.S <svabox@gmail.com> a écrit :
On 07.06.2017 16:20, Gwendal Roué wrote:
Le 7 juin 2017 à 15:11, Xiaodi Wu <xiaodi.wu@gmail.com < > mailto:xiaodi.wu@gmail.com <xiaodi.wu@gmail.com>>> a écrit :

func f(_ closure:(Int, Int) -> ())
func g(_ closure:((Int, Int)) -> ())
func h(_ closure:((a: Int, b: Int)) -> ())

If one can always write (as in Swift 3):

f { (a, b) in ... }
g { (a, b) in ... }
c { (a, b) in ... }

Then one can easily deal with a badly fit closure signature.

This is most examplified by dictionaries. They always expose (key: Key,
value: Value) tuples (their Element type). Problem is that 'key' and
'value' are identifiers that only matter for dictionaries, not for
dictionary users. It's very important for dictionary users to forget about
tuples, and the `key` and `value` words:

// No pollution
dictionary.map { (name, score) in ... }

That wish (let one easily deal with a badly fit closure signature) is also
asked by Stephen Cellis in his functional explorations.

* generation of closure of correct type if closure is declared inside
function call and arguments have no type annotations, i.e.
//schedule(callback: foo) // disallowed, type mismatch
//schedule(callback: fooClosure) // disallowed, type mismatch

// allowed. compiler will generate closure of type ((Int,Int))->() from
this code
schedule { x,y in }

// type mismatch, this syntax defines closure of (Int,Int)->() type
//schedule { (x: Int, y: Int) in }

But because all of this are additional features that can be added later,
and each required to be reviewed/discussed in details, core team can decide
to delay such 'fix' for after-release period. Let's wait and see what core
team had to say about this subject.

I can't wait to hear from the core team.

Gwendal

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

Please refrain from personal attacks. Are we reading the same sentence? I
see that you literally listed three proposals (and “before”) and blamed
them for “regressions” that you want to reverse. If that’s not your
meaning, what do you mean?

In the conversation you can see that Vladimir stated that proposing to
revert this proposal would also mean reverting other previous proposals.

Gwendal is only asking back if the bug was introduced by the changesets
related to those proposals or by the changesets related to SE-0110.

Asking back is very different from blaming those proposals, or asking for
reversal of Swift 3 proposals. SE-0110 may be an obvious extension of the
proposed goal, but it is clear that it has been implemented in Swift 4, and
that the consequences are derived of those changesets.

Those "unwanted" consequences can be reverted by temporarily reverting
SE-0110, without touching any other previous proposal.

In no place I see Gwendal asking for full reversal of 5 proposals. He is
just voicing something that a lot of app developers and library maintainers
are not going to understand in September.

For example, you can see all the changes needed in RXSwift so that it
compiles to Swift 4:

I would not want to migrate to Swift 4 an app using such framework.

I asked you in my first reply to you, is your view that the distinction
itself between (Int, Int) -> Int and ((Int, Int)) -> Int is problematic? If
so, this is a very different discussion from seeking solutions to mitigate
particular ergonomic issues that arise from the change. However, you have
not answered the question.

As far as I can see, the bigger usability regressions are caused when using
generics, specially in collections and functional utilities. When you
specify that type with a tuple, as in Dictionary, then all the related
methods start receiving closures receiving tuples.

Notice that allowing some syntactic sugar on the call site of those
closures may not be enough, since you may have stored or passed a closure
for customization purposes.

This behavior is so hurtful to functional style programming, that I think
either SE-0110 should be reverted, or alternatives like this Susan proposal
should be implemented. However, reverting may be safer and faster than
adding more new code to the compiler with such short time until Swift 4
release.

Of course that´s the Core team call, if the gains in the compiler code is
so great then we'll have to live with this regression. After WWDC I hope
that they notify their decision.

···

On 7 Jun 2017, at 18:10, Xiaodi Wu via swift-evolution < swift-evolution@swift.org> wrote:

On Wed, Jun 7, 2017 at 10:03 Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 7 juin 2017 à 15:52, Xiaodi Wu <xiaodi.wu@gmail.com> a écrit :

Let’s clarify: you just wrote that you have problems with SE-0029,
SE-0066, SE-0110, and “before,” did you not?

WTF? No I did not (citation below)!

Le 7 juin 2017 à 15:02, Gwendal Roué <gwendal.roue@gmail.com> a écrit :

Le 7 juin 2017 à 14:42, Vladimir.S <svabox@gmail.com> a écrit :

Gwendal, again, you are proposing to revert not just SE-0110 and SE-0066
but mainly SE-0029 "Remove implicit tuple splat behavior from function
applications"
(GitHub - apple/swift-evolution: This maintains proposals for changes and user-visible enhancements to the Swift Programming Language.
proposals/0029-remove-implicit-tuple-splat.md)

Do you mean that the regressions Stephen and I have shown have been
introduced not only by SE-0110, but before SE-0066, and SE-0029?

Your attitude is obnoxious. Enough Swift evolution for me today.

--
Víctor Pimentel

It looks like some people in this mailing list are horrified by this "request" (not a feature request, but a request that Swift 3 behavior is restored, actually).

What could be the reasons for such a bad reaction?

1: measurable runtime overhead (slower programs in some cases, without any obvious way for the developper to notice where is the extra cost)
2: measurable compiler overhead (slower compilation)
3: implementation complexity (slower swift progress, technical debt, etc.)
4: other?

I understand 1. We are all fascinated by C++ and Rust "zero-overhead". If this is the main concern of the community, then we may focus the discussion of that very precise topic.

I can live with 2 (just a personal subjective preference)

About 3: I can not tell because I lack the necessary skills.

4: enlighten us!

Gwendal

···

Le 7 juin 2017 à 20:33, Gwendal Roué <gwendal.roue@gmail.com> a écrit :

For example, take those three functions:

  func f(_ closure:(Int, Int) -> ())
  func g(_ closure:((Int, Int)) -> ())
  func h(_ closure:((a: Int, b: Int)) -> ())

If one can always write (as in Swift 3):

  f { (a, b) in ... }
  g { (a, b) in ... }
  c { (a, b) in ... }

Then one can easily deal with a badly fit closure signature.

This is most examplified by dictionaries. They always expose (key: Key, value: Value) tuples (their Element type). Problem is that 'key' and 'value' are identifiers that only matter for dictionaries, not for dictionary users. It's very important for dictionary users to forget about tuples, and the `key` and `value` words:

  // No pollution
  dictionary.map { (name, score) in ... }

Yes, I unfortunately agree. The best term I have heard to describe this list is “Combative”.

I wish actual user experience / human factors was a greater consideration here, and that often requires little touches that are immediately dismissed from this list as being not-significant/important enough. But those little touches add up to make the overall experience… and our experience has been suffering as a result.

I don’t mean to offend, and I feel like I am learning a lot from everyone here (I am an experienced mac/iOS developer, but I mainly work as a designer and teacher). I just really wish the list was more open to a range of voices.

Thanks,
Jon

···

On Jun 7, 2017, at 11:33 AM, Gwendal Roué via swift-evolution <swift-evolution@swift.org> wrote:

So far, the answer to the ergonomics regression reports have been much too often negative. I wish ergonomics had better support in the community. Very few regular developers talk here, unfortunately.

Le 7 juin 2017 à 17:15, Vladimir.S <svabox@gmail.com <mailto:svabox@gmail.com>> a écrit :

While SE-0025 was generally regarded as unfortunate, the thousands of emails that followed relitigating it were much, much worse.

The removal of implicit tuple splatting, which is *not* SE-0110, was approved on the understanding that it would be a regression until explicit tuple splatting is introduced. This tradeoff was considered and approved. It’s clear that you disagree, but that is not grounds to divert a necessary discussion on mitigating SE-0110 into relitigating something else.

Push me out if you want, but will you push out those blatant wounds out as well?
Example 1
- return columns.index { (column, _) in column.lowercased() == lowercaseName }
+ return columns.index { $0.0.lowercased() == lowercaseName }

Why not
columns.index { (arg: (column: String, _: Int)) in arg.column.lowercased() == lowercaseName }
?

It would works, but it's less than ideal: the `args` name has no meaning. We have an extra type declaration instead of type inference.

Yes, as I said below, it's clearly 'less than ideal', but I do believe this variant is much better than what you shown as regression example. That was my main point.

As for 'arg' name.. well.. naming is very hard task ;-) I didn't think about name in this case, probably 'arg' should be 'column' and current 'column' should be just 'name', if I understand correctly:
columns.index { (column: (name: String, _: Int)) in column.name.lowercased() == lowercaseName }

Btw, I forgot to mention another one good(IMO) suggestion for tuple argument destructuring syntax in closure:

* allow type inference for tuple parts in already allowed syntax i.e. :
columns.index { (column: (name, _)) in column.name.lowercased() == lowercaseName }

I even feel like this one can be the best candidate *for now* as it just relaxes requirements for type annotation, allows type inference, that seems very naturally for closures. Similar like we can omit type for arguments in argument list, but just for tuple parts. This could be just additive sugar for closures that will help to migrate to Swift 4 with *less* pain. Then we'll have an ability to discuss best solution for tuple argument deconstruction in details in separate proposal, after Swift4 is released.

> It's much longer. The clarity is
> tremendously reduced.

I just can't agree with this. IMO it is slightly longer, and clarity is slightly reduced.

- return columns.index { (column, _) in column.lowercased() == lowercaseName }
+ return columns.index { (arg: (column: String, _)) in arg.column.lowercased() == lowercaseName }

Yes, I understand that first syntax short and not verbose, but the alternative you provided IMHO much worse than explicit type declaration in closure.

It's not an "alternative": it's Swift 3.

Maybe you did already profit from it, but did not notice.

Sorry, I don't understand.
I assume you had this code for Swift 3:
return columns.index { (column, _) in column.lowercased() == lowercaseName }
and you need to convert it to Swift 4, so you are saying "look how this is ugly":
return columns.index { $0.0.lowercased() == lowercaseName }
and I'm saying "we can instead in Swift 4 have this":
return columns.index { (arg: (column: String, _)) in arg.column.lowercased() ==
  lowercaseName }
which is IMO much better *and* will compile in Swift 3 also.

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

Can't compile something like this even in Swift 3, could you provide a small code snippet for this?

Sure:

     letmapping: [String: String] = ...
     mapping.map { (mappedColumn, baseColumn) -> (Int, String) in ... }

Thank you, yes, here is the same:

let mapping: [String: String] = ["1":"2", "3":"4"]
let _ = mapping.map { (item: (mappedColumn: String, baseColumn: String)) -> (Int, String) in (0, item.baseColumn) }

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

Same, why not

.map { (arg: (table: String, columns: [String])) in "\(arg.table)(\(arg.columns.sorted().joined(separator: ", ")))" }

Same answer: the extra `args` works, but we still have an ergonomics regression from Swift 3.

Agree. We have some ergonomics regression in this case. But for now, it is IMO the best solution we have to port such Swift 3 code to Swift 4. Let's wait what core team will decide about this.

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

Same.

Indeed.

See also messages from Stephen Cellis, who shows how other kinds of developer code has lost expressivity and clarity with those changes that have been "considered and approved".

Gwendal, no one saying that new syntax is better, that it is good thing that we lost the short syntax for tuple argumment deconstructions in closures.

Good to hear :-)

But there is just no easy/obvious way to keep that syntax in Swift 4. The problem can't be solved just by not implementing SE-0110, as in Swift4 we should have two separate function types: one that takes single tuple argument and second that accepts a list of arguments, i.e. (Int,Int)->() and ((Int,Int))->() should be two different types now.

Of course I understand that.

I expect the compiler to perform the expected conversions for ergonomics' sake.

I can understand that this sugar could be added *after* current problems which are not related to ergonomics are solved. I just want to make it clear that there is an ergonomics problem *now*, and that solving it should have a very high priority.

I understand and even agree that we should provide *good* solution for tuple argument destructuring in closure before Swift 4 release. But it seems like we can't propose/discuss/implement the *best* solution due to lack of time.

This is not just SE-0110, this is also SE-0066, so, to be correct, you should propose to revisit it also.

Please look here:

func foo(_ x: Int, _ y: Int) {} // type(of: foo) should be (Int, Int)->()
func bar(_ x (Int, Int)) {} // type(of: bar) should be ((Int, Int))->()

The above is described in SE-0066. Then, you have a closure constants:

var fooClosure = {(x: Int, y: Int) in }
var barClosure = {(x: (Int, Int)) in }

what should be types of these closures? Obvious the same: (Int,Int)->() and ((Int,Int))->() respectively.

I guess you mean, not the same: (Int, Int) -> () vs. ((Int, Int)) -> ().

Yes, "same" as for functions, when first is (Int,Int)->() and second is ((Int,Int))->() which should be different types in Swift 4.

Then you have a func that accepts ((Int,Int))->Int closure:

func schedule(callback: ((Int,Int))->()) {..}

, given type of foo func is (Int, Int)->() , do you suggest to allow sending foo to 'schedule' func? The same question is for fooClosure

schedule(callback: foo) // ??
schedule(callback: fooClosure) // ??

Yes, I do. For ergonomics' sake.

Below I agreed that potentially this could be a solution, when both function types can be 'interchangeable' while keeping their correct types. Unfortunately it's out of my knowledge if such solution can work at all(it looks like some very exceptional rule, so at least it requires a detailed discussion), here we need some comments from core team/compiler developers.

Probably we can(if technically possible, I don't know) to always allow sending of function/closure with list of arguments when function with one tuple is required. I don't know how such exceptional rule would looks like inside type system of Swift, what should be result of 'foo is ((Int,Int))->()' then and 'type(of:foo) == type(of:bar)' in such case.
But this requires a formal proposal, review period and implementation(as I understand, better before Swift 4 release). Probably you can submit such proposal, go through the review period and help with implementation.

Seriously I don't know if I have the skills to write such a proposal. It dives much too deep in the compiler internals for me to be efficient in the proposal process.

I thus hope that I did succeed showing how seriously bad are the regressions induced by SE-0110, and that a skilled language designer will sponsor an ergonomics-rescue proposal.

So far, the answer to the ergonomics regression reports have been much too often negative. I wish ergonomics had better support in the community. Very few regular developers talk here, unfortunately.

As I understand, there is no negative responds to "ergonomics regression reports", everyone understands this, but there is an attempt to explain that there is no such simple solution like "let's just drop SE-0110" and "let's just make it work as in Swift 3".

In this case we'll have the same user-friendly closure/function parameters expirience but with respect to correct function types.

That would be just great!

But currently we have a situation: argument of type ((Int,Int))->() is required, and we provide argument of another type : (Int,Int)->() i.e. incorrect type.
The only obvious solution here is using the common rule for type mismatch - disallow this.

And the obvious and easy way leads to regressions.

Currently we have a number of suggestions how we can improve usability for the discussed problem:

* use 'let' syntax in closure argument list to deconstruct tuple argument

* use doubled parenthesis to deconstruct tuple argument: { ((key, value)) in .. }

Both 'let' and doubled parenthesis fail in ergonomics.

Let me explain you why: when you provide a closure to a function, do you know if the function expects a single-tuple input, or a multiple-arguments input? You don't know.

For example, take those three functions:

func f(_ closure:(Int, Int) -> ())
func g(_ closure:((Int, Int)) -> ())
func h(_ closure:((a: Int, b: Int)) -> ())

If one can always write (as in Swift 3):

f { (a, b) in ... }
g { (a, b) in ... }
c { (a, b) in ... }

Then one can easily deal with a badly fit closure signature.

This is most examplified by dictionaries. They always expose (key: Key, value: Value) tuples (their Element type). Problem is that 'key' and 'value' are identifiers that only matter for dictionaries, not for dictionary users. It's very important for dictionary users to forget about tuples, and the `key` and `value` words:

// No pollution
dictionary.map { (name, score) in ... }

That wish (let one easily deal with a badly fit closure signature) is also asked by Stephen Cellis in his functional explorations.

Well, I and Xiaodi trying to point out, that the 'problem' is not in some "accidentally accepted", "single separate not-well-considered proposal" SE-0110, which can be 'just' dropped out/revisited and all will still be fine.

There is also an effect of not-fully-implemented proposals in Swift 3 and long running bugs, which should be fully-implemented/fixed in Swift 4. Probably because of this you think that exactly SE-0110 breaks the code, while this is just not true.

In another thread, Mark Lacey mentioned that he thinks that it probably possible to allow this:
dictionary.map { name, score in ... }
, in this case(when there is no type annotations and closure declared inside func call) compiler can generate closure of correct type depending of what is required. But this will be still disallowed:
dictionary.map { (name: String, score: Int) in ... }
and this disallowed:
func foo(name: String, score: Int) {}
dictionary.map(foo) // type mismatch

As I understand the situation currently, we just have no any time for *best* solution for the discussed subject before Swift 4 release : as *best* solution should go through formal proposal/discussion/acceptance/probably revisiting and additional discussions/implementation etc.
So, I do believe the task currently is to find the acceptable solution, that can be implemented without huge effort and in short time, to minimize the pain of moving Swift 3 code with tuple argument destructuring in closure to Swift 4.
Then we can discuss *best* option as additional change after Swift 4 release.

I'd like someone correct me if I'm wrong here.

Vladimir.

···

On 07.06.2017 21:33, Gwendal Roué wrote:

On 07.06.2017 16:20, Gwendal Roué wrote:

Le 7 juin 2017 à 15:11, Xiaodi Wu <xiaodi.wu@gmail.com >>>> <mailto:xiaodi.wu@gmail.com> <mailto:xiaodi.wu@gmail.com>> a écrit :

* generation of closure of correct type if closure is declared inside function call and arguments have no type annotations, i.e.
//schedule(callback: foo) // disallowed, type mismatch
//schedule(callback: fooClosure) // disallowed, type mismatch

// allowed. compiler will generate closure of type ((Int,Int))->() from this code
schedule { x,y in }

// type mismatch, this syntax defines closure of (Int,Int)->() type
//schedule { (x: Int, y: Int) in }

But because all of this are additional features that can be added later, and each required to be reviewed/discussed in details, core team can decide to delay such 'fix' for after-release period. Let's wait and see what core team had to say about this subject.

I can't wait to hear from the core team.

Gwendal

    func notOverloaded1(_ closure: (Int, Int) -> Int) -> String { return "notOverloaded1" }
    func notOverloaded2(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "notOverloaded3" }
    
    func overloaded(_ closure: (Int, Int) -> Int) -> String { return "overloaded 1" }
    func overloaded(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "overloaded 2" }
    
    // not overloaded => not ambiguous
    notOverloaded1 { x, y in x + y }
    notOverloaded1 { (x, y) in x + y }
    notOverloaded1 { _ in 1 }
    notOverloaded2 { x, y in x + y }
    notOverloaded2 { (x, y) in x + y }
    notOverloaded2 { _ in 1 }
    
    // overloaded => resolve ambiguity on closure argument, when possible
    overloaded { x, y in x + y } // "overloaded 1"
    overloaded { (x, y) in x + y } // "overloaded 1"
    overloaded { t in t.lhs + t.rhs } // "overloaded 2"
    overloaded { (t) in t.lhs + t.rhs } // "overloaded 2”

This is exactly what happens today as a result of SE-0110 since the first two calls take two arguments, and the next two take a single argument.

No, as a playground quickly reveals:

I was specifically referring to the last four statements but could have made that more clear.

    func notOverloaded1(_ closure: (Int, Int) -> Int) -> String { return "notOverloaded1" }
    func notOverloaded2(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "notOverloaded3" }
    
    // not overloaded => not ambiguous
    notOverloaded1 { x, y in x + y }
    notOverloaded1 { (x, y) in x + y }
    notOverloaded1 { _ in 1 } // Swift 4 error

This really should be an error.

I argue this is a subjective statement, and my own subjective statement is that it should not to be an error. So that { _ in ... } would always mean "I don't care".

It’s one thing to say that { x, y in } should work as it used to in the case where a closure taking a tuple is expected, but something else entirely to say that _ should work in any case, regardless of whether a closure taking N arguments or a closure taking an N-tuple is expected for any N. I can see a desire to make _ work for any tuple, and _, _ work for a two-tuple based on context if e.g. “x, y in” also works, but not for _ working where a closure requiring N independent arguments is expected.

Yes. That's why it's subjective. You argue that you don't like it, not that it's impossible to do. Because you can't prove it's impossible to do.

I’m not trying to argue that it’s impossible to do. I don’t think it’s a good idea at all. That’s subjective. Me saying “that really should be an error” is a subjective statement. I don’t have to say “This is a subjective statement” to make a subjective statement.

Because I have shown that it is.
In a nice manner that is easy to developers.

    notOverloaded2 { x, y in x + y } // Swift 4 error
    notOverloaded2 { (x, y) in x + y } // Swift 4 error

In these cases using the tuple labels works as well (as it does in your examples where the overloaded function is involved). I’m not arguing that it’s as good as being able to do the apparent destructuring, but just want to point out that it works. I didn’t get the sense from your email that you found that objectionable in the cases involving overloads, but perhaps you do.

    notOverloaded2 { _ in 1 }

Those errors are the famous regressions we want to fix.

    overloaded { _ in 1 } // error: ambiguous use of ‘overloaded'

With SE-0110 in its current form, this calls the tuple version since there is a single closure parameter listed.

It would be nicer if. { _ in ... } would always mean "I don't care". With an ambiguity in the sample code we're looking at, and a compiler error.

How does making something that is unambiguous today actually be ambiguous improve things?

I *do* suggest a specific handling of { _ ... }. I have shown how it can be implemented in a non-ambiguous fashion.

Your own comment says this should be considered ambiguous. It’s unambiguous now. What I am asking is how is that an improvement?

Mark

···

On Jun 9, 2017, at 12:55 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 9 juin 2017 à 09:41, Mark Lacey <mark.lacey@apple.com <mailto:mark.lacey@apple.com>> a écrit :

On Jun 9, 2017, at 12:12 AM, Gwendal Roué <gwendal.roue@gmail.com <mailto:gwendal.roue@gmail.com>> wrote:

Please don't act as if I did not.

Now the specific case of { _ ... } is interesting, but I wouldn't make it as important as the other regressions.

I should make an exhaustive compilation of Swift 4 regressions, so that you opponents can see how *far* things have changed, fixing a few corner cases, and breaking tons of valid code. At least we could discuss each case in isolation.

Gwendal

I’m not trying to argue that it’s impossible to do. I don’t think it’s a good idea at all. That’s subjective. Me saying “that really should be an error” is a subjective statement. I don’t have to say “This is a subjective statement” to make a subjective statement.

Yes, sorry: It's so easy to sound assertive even when we just want to share and communicate opinions.

I *do* suggest a specific handling of { _ ... }. I have shown how it can be implemented in a non-ambiguous fashion.

Your own comment says this should be considered ambiguous. It’s unambiguous now. What I am asking is how is that an improvement?

I suggest { _ in ... } is ambiguous only in case of function overloading. In this case, you have { (_) in ... } and { (_,_) in ... } for disambiguation:

    func overloaded(_ closure: (Int, Int) -> Int) -> String { return "overloaded 1" }
    func overloaded(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "overloaded 2" }
    overloaded { _ in 1 } // error: ambiguous use of ‘overloaded'
    overloaded { (_) in 1 } // "overloaded 1”
    overloaded { (_, _) in 1 } // "overloaded 2”

When a function is not overloaded, then { _ in ... } would always mean "I don't care", and is always accepted except for closures that take no argument at all:

    func f1(_ closure: () -> Int) -> String { return "f1" }
    func f2(_ closure: (Int) -> Int) -> String { return "f2" }
    func f3(_ closure: (Int, Int) -> Int) -> String { return "f3" }
    func f4(_ closure: ((lhs: Int, rhs: Int)) -> Int) -> String { return "f4" }

    f1 { _ in 1 } // error

    f2 { _ in 1 } // OK, you don't care

    f3 { _ in 1 } // OK, you don't care
    f3 { (_, _) in 1 } // OK, just what I expected!

    f4 { _ in 1 } // OK, you don't care
    f4 { (_) in 1 } // OK, just what I expected!
    f4 { (_, _) in 1 } // OK, maybe you use tuple splatting somewhere else and want to be consistent

All this is *possible*. And I don't see how it breaks anything. On the other side, it eases everyday life, reduces clutter, and avoids useless punctuation.
Gwendal

···

Le 9 juin 2017 à 10:07, Mark Lacey <mark.lacey@apple.com> a écrit :

Let’s clarify: you just wrote that you have problems with SE-0029,
SE-0066, SE-0110, and “before,” did you not?

WTF? No I did not (citation below)!

Gwendal, again, you are proposing to revert not just SE-0110 and SE-0066
but mainly SE-0029 "Remove implicit tuple splat behavior from function
applications"
(
https://github.com/apple/swift-evolution/blob/master/proposals/0029-remove-implicit-tuple-splat.md
)

Do you mean that the regressions Stephen and I have shown have been
introduced not only by SE-0110, but before SE-0066, and SE-0029?

Your attitude is obnoxious. Enough Swift evolution for me today.

Please refrain from personal attacks. Are we reading the same sentence? I
see that you literally listed three proposals (and “before”) and blamed
them for “regressions” that you want to reverse. If that’s not your
meaning, what do you mean?

In the conversation you can see that Vladimir stated that proposing to
revert this proposal would also mean reverting other previous proposals.

Gwendal is only asking back if the bug was introduced by the changesets
related to those proposals or by the changesets related to SE-0110.

Asking back is very different from blaming those proposals, or asking for
reversal of Swift 3 proposals.

See, I read that sentence as a rhetorical question, intensifying his claim
of not merely one but all of these proposals being regressions. If that’s
is not what’s intended, that is fine, but he is certainly capable of
replying to my question about his meaning without declaring me to be
‘obnoxious.’

SE-0110 may be an obvious extension of the proposed goal, but it is clear

that it has been implemented in Swift 4, and that the consequences are
derived of those changesets.

Those "unwanted" consequences can be reverted by temporarily reverting
SE-0110, without touching any other previous proposal.

In no place I see Gwendal asking for full reversal of 5 proposals. He is
just voicing something that a lot of app developers and library maintainers
are not going to understand in September.

For example, you can see all the changes needed in RXSwift so that it
compiles to Swift 4:

Compile under Swift 4 by volodg · Pull Request #1282 · ReactiveX/RxSwift · GitHub

I would not want to migrate to Swift 4 an app using such framework.

I asked you in my first reply to you, is your view that the distinction
itself between (Int, Int) -> Int and ((Int, Int)) -> Int is problematic? If
so, this is a very different discussion from seeking solutions to mitigate
particular ergonomic issues that arise from the change. However, you have
not answered the question.

As far as I can see, the bigger usability regressions are caused when
using generics, specially in collections and functional utilities. When you
specify that type with a tuple, as in Dictionary, then all the related
methods start receiving closures receiving tuples.

Notice that allowing some syntactic sugar on the call site of those
closures may not be enough, since you may have stored or passed a closure
for customization purposes.

Can you illustrate this with an example? I’m not sure I understand what
you’re getting at here.

This behavior is so hurtful to functional style programming, that I think

either SE-0110 should be reverted, or alternatives like this Susan proposal
should be implemented.

To be clear, what you and Gwendal are objecting to is the loss of tuple
splatting, is it not? Again, SE-0110 is only the final piece; SE-0029 is
what removed implicit tuple splatting. As that proposal said, a properly
designed explicit tuple splatting can be considered if there’s demand, and
it was understood that removing this functionality would be a regression
and nonetheless the decision for removal was still deliberately undertaken.
Re-reading that proposal, the rationale was that it never worked correctly
to begin with, and the barrier for bringing it back is that it requires
considerable effort to design and implement the feature correctly.

However, reverting may be safer and faster than adding more new code to the

···

On Wed, Jun 7, 2017 at 14:13 Víctor Pimentel Rodríguez <vpimentel@tuenti.com> wrote:

On 7 Jun 2017, at 18:10, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:
On Wed, Jun 7, 2017 at 10:03 Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 7 juin 2017 à 15:52, Xiaodi Wu <xiaodi.wu@gmail.com> a écrit :
Le 7 juin 2017 à 15:02, Gwendal Roué <gwendal.roue@gmail.com> a écrit :
Le 7 juin 2017 à 14:42, Vladimir.S <svabox@gmail.com> a écrit :

compiler with such short time until Swift 4 release.

Of course that´s the Core team call, if the gains in the compiler code is
so great then we'll have to live with this regression. After WWDC I hope
that they notify their decision.

--
Víctor Pimentel