Proposal: Always flatten the single element tuple

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)) -> ()
    let a = { (x: Int, y: Int) -> Int in ... }
    let b = { ((x: Int, y: Int)) -> Int in ... }

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:

    func wantsTwoArguments(_ closure: (Int, Int) -> Int) { closure(1, 2) }
    wantsTwoArguments { a, b in a + b }
    wantsTwoArguments { (a, b) in a + b }
    wantsTwoArguments { t in t.0 + t.1 } // OK, maybe not

    func wantsATupleArgument(_ closure: ((Int, Int)) -> Int) { closure((1, 2)) }
    wantsATupleArgument { a, b in a + b }
    wantsATupleArgument { (a, b) in a + b }
    wantsATupleArgument { t in t.0 + t.1 }
    
    func wantsANamedTupleArgument(_ closure: ((lhs: Int, rhs: Int)) -> Int) { closure((lhs: 1, rhs: 2)) }
    wantsANamedTupleArgument { a, b in a + b }
    wantsANamedTupleArgument { (a, b) in a + b }
    wantsANamedTupleArgument { t in t.lhs + t.rhs }

This gives us the ability to deal with unfitted function signatures. For example, most Dictionary methods. Yes, they are usually unfitted:

    extension Dictionary {
        func forEach(_ 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:

    let scores: [String: Int] = ... // [playerName: score]
    scores.forEach { name, score in
        print("\(name): \(score)")
    }

Do you see?

This post is the one that makes most sense to me! Be strict on definition but flexible on use.

···

On 8 Jun 2017, at 11:17, Gwendal Roué via swift-evolution <swift-evolution@swift.org> 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:

Gwendal

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

Side note: when converting a project to Swift 4, I’ve encountered quite a few Quality of Life regressions that could also be related:

Regression 1

When using this library https://github.com/socketio/socket.io-client-swift:

public typealias NormalCallback = ([Any], SocketAckEmitter) -> Void

class Socket {
    func on(_ event: String, callback: @escaping NormalCallback) -> UUID { … }
}

My code had to be modified from:

socket.on(“message”) { _ in
    print(“got message")
}

to:

socket.on(“message”) { (_, _) in
    print(“got message")
}

Regression 2

When using RxSwift: GitHub - ReactiveX/RxSwift: Reactive Programming in Swift (simplified example):

public class Driver<E> {
    func drive(onNext: ((E) -> Void)) -> Disposable {}
}

I had to change the following code:

let driver: Driver<Void> = …
driver.drive(onNext: {
    print(“onNext")
})

to:

let driver: Driver<Void> = …
driver.drive(onNext { _ in
    print(“onNext")
})

Regression 3

Also with RxSwift (simplified example):

public class Driver<E> {
    func onNext(_ element: E) { … }
}

I had to change the following code:

let driver: Driver<Void> = …
driver.onNext()

to:

let driver: Driver<Void> = …
driver.onNext(())

Conclusion

Basically, these regressions add synctactic pollution to quite a few cases turning around tuples and Void.

David.

···

On 8 Jun 2017, at 12:08, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

On 8 Jun 2017, at 11:17, Gwendal Roué via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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:

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)) -> ()
    let a = { (x: Int, y: Int) -> Int in ... }
    let b = { ((x: Int, y: Int)) -> Int in ... }

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:

    func wantsTwoArguments(_ closure: (Int, Int) -> Int) { closure(1, 2) }
    wantsTwoArguments { a, b in a + b }
    wantsTwoArguments { (a, b) in a + b }
    wantsTwoArguments { t in t.0 + t.1 } // OK, maybe not

    func wantsATupleArgument(_ closure: ((Int, Int)) -> Int) { closure((1, 2)) }
    wantsATupleArgument { a, b in a + b }
    wantsATupleArgument { (a, b) in a + b }
    wantsATupleArgument { t in t.0 + t.1 }
    
    func wantsANamedTupleArgument(_ closure: ((lhs: Int, rhs: Int)) -> Int) { closure((lhs: 1, rhs: 2)) }
    wantsANamedTupleArgument { a, b in a + b }
    wantsANamedTupleArgument { (a, b) in a + b }
    wantsANamedTupleArgument { t in t.lhs + t.rhs }

This gives us the ability to deal with unfitted function signatures. For example, most Dictionary methods. Yes, they are usually unfitted:

    extension Dictionary {
        func forEach(_ 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:

    let scores: [String: Int] = ... // [playerName: score]
    scores.forEach { name, score in
        print("\(name): \(score)")
    }

Do you see?

This post is the one that makes most sense to me! Be strict on definition but flexible on use.

Gwendal

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

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

    Just a thought

    if parentheses is important, why the tuples are not?

This is stated on the proposal (and not in previous proposals):

https://github.com/apple/swift-evolution/blob/master/proposa
ls/0110-distingish-single-tuple-arg.md

  *

    We understand that this may be a departure from the current
convention that a set
    of parentheses enclosing a single object are considered semantically
meaningless,
    but it is the most natural way to differentiate between the two
situations
    described above and would be a clearly-delineated one-time-only
exception.

    <https://github.com/apple/swift-evolution/blob/master/propos
als/0110-distingish-single-tuple-arg.md#impact-on-existing-code>

This proposal marks a one-time-only exception, to differentiate the
parenthesis needed to enclose a list of closure parameters and the
parenthesis needed for tuples. That's adding an exception for implementing
a regression.

The more I think about it, the more I hate* this proposal.

* Well, not _hate_, let's say slightly dislike :P

Please look here:
https://github.com/apple/swift-evolution/blob/master/proposa
ls/0066-standardize-function-type-syntax.md

into "Proposed solution" section:

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

let f: () -> Int // function with no parameters
let g: (()) -> Int // function taking a single () parameter
let h: ((())) -> Int // function taking a single () parameter

f(); g(()); h(()) // correct
f(()); g(); h() // errors

Do you also hate* SE-0066?

I'm certainly not a fan, although I must say that it's funny to me to think
of the usefulness of a closure that only accepts a single parameter of type
"()", and how important it's to be able to model it. It was beautiful to me
how tuples and closure arguments were interchangeable, and I hope that some
day that feature will be back.

I'm also not a fan of SE-0029, the original proposal that removed tuple
splat and linked from SE-0110. I'm fine if it was better for the compiler
to remove that hacky implementation, but that proposal certainly has some
disdain for implicit tuple splat:

This doesn’t seem like a positive contribution - this seems like a

"clever" feature, not a practical one.

And going back to SE-0110, I dislike this part too:

The current behavior violates the principle of least surprise

People seems to get more surprised when you migrate some perfectly
reasonable code and have to use an intermediate tuple to achieve the same
effect as the code example in SE-0110 shows.

After rereading the proposals the motivation sections read more like "we
don't like this feature and it's useless" more than "we don't like this
implementation and it's dragging us". Looking at the final implementation
of this proposal (the third one!), I don't think the resulting code is
easier to maintain, actually the Swift 4 mode seems more complex:

I'm sorry, but I like this feature. Apart from Dictionary and similar, I'm
really going to miss being able to model every type of closure with the
type (T) -> U.

···

On Thu, Jun 8, 2017 at 3:01 PM, Vladimir.S <svabox@gmail.com> wrote:

On 08.06.2017 12:17, Víctor Pimentel Rodríguez via swift-evolution wrote:

On Thu, Jun 8, 2017 at 5:15 AM, Susan Cheng via swift-evolution < >> swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

--
Víctor Pimentel

I regret my "Absolutely nobody" snippet. Some people like the `key` and `value` identifiers.

Those people are the people who write the standard library, people who write blog posts about Swift novelties, and people who write experimental software around Swift. All very valuable people.

There are other kinds of developers, who don't really care about the beauty of dictionary design and implementation. They just want a container that does what it's supposed to do: map stuff to other stuff. Not keys to values.

To them, a dictionary is like a screwdriver: when you use it, you don't want to think about it much.

And the recent changes force us to see uninteresting implementation details that obscure everyday code.

Gwendal

···

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

This gives us the ability to deal with unfitted function signatures. For example, most Dictionary methods. Yes, they are usually unfitted:

    extension Dictionary {
        func forEach(_ 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:

    let scores: [String: Int] = ... // [playerName: score]
    scores.forEach { name, score in
        print("\(name): \(score)")
    }

Do you see?

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

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.

···

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:

Gwendal

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

Xiaodi, Adrian, you are actively pushing so that something that was
allowed, well compiled (no runtime issue), and covered actual uses cases,
becomes forbidden.

To be clear, I’m not pushing to forbid anything at all. SE-0110 prohibited
this a long time ago.

Without any developer advantage that would somehow balance the change.

That's called a regression.

And what's the rationale, already?

Surely, the time to ask that question was during review of SE-0110. Today’s
question is: how do we improve the user experience given that tuples and
argument lists are now distinct? Is your starting point that you reject
that conclusion altogether? If so, we’re simply having distinct discussions.

A sense of compiler aesthetics? Since when a sense of compiler aesthetics

···

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

is more valuable than a sense of code aesthetics? Aren't both supposed to
walk together as a pair?

Gwendal

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

IMO, if tuples and argument lists are to be distinguished in any way, it
is imperative that f3(+) and f4(+), and some of your other examples, _not_
work.

After all, if a tuple is not an argument list, it should possible to have
a function of type ((Int, Int)) -> Int and a function of type (Int, Int) ->
Int share the same name (IIUC, it’s a known bug that this does not
currently work). Quite simply, there’s a type mismatch if you pass sum1 to
f3–what happens if there’s a distinct, overloaded sum1 that takes a single
tuple?

Chris’s suggestion restores a syntactic convenience without touching the
type system. What you are arguing for is essentially making ((Int, Int)) ->
Int and (Int, Int) -> Int synonymous again, either in some or in all
contexts.

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

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

Le 7 juin 2017 à 12:03, Adrian Zubarev via swift-evolution < >> swift-evolution@swift.org> a écrit :

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 }

Despite Chris Lattern being a semi-god, his double-parenthesis suggestion
cruelly lacks in terms of user ergonomics. The compiler should be able to
deal with the following code snippet, just like Swift 3 does:

    // two arguments
    func f1(_ closure: (Int, Int) -> Int) { closure(1, 2) }

[...]

Here is the full extent of the remarquable Swift 3 ergonomics. This full
snippet compiles in Swift 3:

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

    // two arguments
    func f1(_ closure: (Int, Int) -> Int) { closure(1, 2) }
    f1 { lhs, rhs in lhs + rhs }
    f1 { (lhs, rhs) in lhs + rhs }
    f1 { tuple in tuple.0 + tuple.1 }
    f1 { (tuple) in tuple.0 + tuple.1 }
    f1(+)
    f1(sum1)
    f1(sum2)
    f1(sum3)
    f1(sum4)

    // two arguments, with documentation names: identical
    func f2(_ closure: (_ a: Int, _ b: Int) -> Int) { closure(1, 2) }
    f2 { lhs, rhs in lhs + rhs }
    f2 { (lhs, rhs) in lhs + rhs }
    f2 { tuple in tuple.0 + tuple.1 }
    f2 { (tuple) in tuple.0 + tuple.1 }
    f2(+)
    f2(sum1)
    f2(sum2)
    f2(sum3)
    f2(sum4)

    // one tuple argument
    func f3(_ closure: ((Int, Int)) -> Int) { closure((1, 2)) }
    f3 { lhs, rhs in lhs + rhs }
    f3 { (lhs, rhs) in lhs + rhs }
    f3 { tuple in tuple.0 + tuple.1 }
    f3 { (tuple) in tuple.0 + tuple.1 }
    f3(+)
    f3(sum1)
    f3(sum2)
    f3(sum3)
    f3(sum4)

    // one keyed tuple argument
    func f4(_ closure: ((a: Int, b: Int)) -> Int) { closure((a: 1, b:
2)) }
    f4 { lhs, rhs in lhs + rhs }
    f4 { (lhs, rhs) in lhs + rhs }
    f4 { tuple in tuple.a + tuple.b }
    f4 { (tuple) in tuple.a + tuple.b }
    f4(+)
    f4(sum1)
    f4(sum2)
    f4(sum3)
    f4(sum4)

Gwendal

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

···

--
Adrian Zubarev
Sent with Airmail

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

Xiaodi, Adrian, you are actively pushing so that something that was allowed, well compiled (no runtime issue), and covered actual uses cases, becomes forbidden. Without any developer advantage that would somehow balance the change.

That's called a regression.

func foo(_: (Int, Int)) {}

func bar(_: Int, _: Int) {}

type(of: foo) == type(of: bar) //=> true - It's a BUG!

And what's the rationale, already? A sense of compiler aesthetics? Since when a sense of compiler aesthetics is more valuable than a sense of code aesthetics? Aren't both supposed to walk together as a pair?

Gwendal

Le 7 juin 2017 à 12:54, Xiaodi Wu xiaodi.wu@gmail.com a écrit :

IMO, if tuples and argument lists are to be distinguished in any way, it is imperative that f3(+) and f4(+), and some of your other examples, not work.

After all, if a tuple is not an argument list, it should possible to have a function of type ((Int, Int)) -> Int and a function of type (Int, Int) -> Int share the same name (IIUC, it’s a known bug that this does not currently work). Quite simply, there’s a type mismatch if you pass sum1 to f3–what happens if there’s a distinct, overloaded sum1 that takes a single tuple?

Chris’s suggestion restores a syntactic convenience without touching the type system. What you are arguing for is essentially making ((Int, Int)) -> Int and (Int, Int) -> Int synonymous again, either in some or in all contexts.

On Wed, Jun 7, 2017 at 05:41 Gwendal Roué via swift-evolution swift-evolution@swift.org wrote:

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

Le 7 juin 2017 à 12:03, Adrian Zubarev via swift-evolution swift-evolution@swift.org a écrit :

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 }

Despite Chris Lattern being a semi-god, his double-parenthesis suggestion cruelly lacks in terms of user ergonomics. The compiler should be able to deal with the following code snippet, just like Swift 3 does:

// two arguments
func f1(_ closure: (Int, Int) -> Int) { closure(1, 2) }

[...]

Here is the full extent of the remarquable Swift 3 ergonomics. This full snippet compiles in Swift 3:

func sum1(_ lhs: Int, _ rhs: Int) -> Int { return lhs + rhs }
func sum2(lhs: Int, rhs: Int) -> Int { return lhs + rhs }
func sum3(tuple: (Int, Int)) -> Int { return tuple.0 + tuple.1 }
func sum4(tuple: (lhs: Int, rhs: Int)) -> Int { return tuple.lhs + tuple.rhs }
// two arguments
func f1(_ closure: (Int, Int) -> Int) { closure(1, 2) }
f1 { lhs, rhs in lhs + rhs }
f1 { (lhs, rhs) in lhs + rhs }
f1 { tuple in tuple.0 + tuple.1 }
f1 { (tuple) in tuple.0 + tuple.1 }
f1(+)
f1(sum1)
f1(sum2)
f1(sum3)
f1(sum4)
// two arguments, with documentation names: identical
func f2(_ closure: (_ a: Int, _ b: Int) -> Int) { closure(1, 2) }
f2 { lhs, rhs in lhs + rhs }
f2 { (lhs, rhs) in lhs + rhs }
f2 { tuple in tuple.0 + tuple.1 }
f2 { (tuple) in tuple.0 + tuple.1 }
f2(+)
f2(sum1)
f2(sum2)
f2(sum3)
f2(sum4)
// one tuple argument
func f3(_ closure: ((Int, Int)) -> Int) { closure((1, 2)) }
f3 { lhs, rhs in lhs + rhs }
f3 { (lhs, rhs) in lhs + rhs }
f3 { tuple in tuple.0 + tuple.1 }
f3 { (tuple) in tuple.0 + tuple.1 }
f3(+)
f3(sum1)
f3(sum2)
f3(sum3)
f3(sum4)
// one keyed tuple argument
func f4(_ closure: ((a: Int, b: Int)) -> Int) { closure((a: 1, b: 2)) }
f4 { lhs, rhs in lhs + rhs }
f4 { (lhs, rhs) in lhs + rhs }
f4 { tuple in tuple.a + tuple.b }
f4 { (tuple) in tuple.a + tuple.b }
f4(+)
f4(sum1)
f4(sum2)
f4(sum3)
f4(sum4)

Gwendal


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

Xiaodi, Adrian, you are actively pushing so that something that was allowed, well compiled (no runtime issue), and covered actual uses cases, becomes forbidden. Without any developer advantage that would somehow balance the change.

That's called a regression.

And what's the rationale, already? A sense of compiler aesthetics? Since when a sense of compiler aesthetics is more valuable than a sense of code aesthetics? Aren't both supposed to walk together as a pair?

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

Here is rationale for acceptance of this proposal:
(Joe Groff jgroff at apple.com)
(Wed Feb 10 11:51:16 CST 2016)

The review of "SE-0029 Remove implicit tuple splat behavior from function applications" ran from February 5...February 9, 2016. The proposal has been accepted for Swift 3. We acknowledge that we're removing a useful feature without providing an equally expressive drop-in replacement. However, maintaining this behavior in the type checker is a severe source of implementation complexity, and actively interferes with our plans to solidify the type system. We feel that removing the existing behavior is a necessary step toward stabilizing the language and toward building a well-designed alternative feature for explicit argument forwarding in the future.

(https://lists.swift.org/pipermail/swift-evolution-announce/2016-February/000033.html\)

We can discuss what sugar we can have to have tuple destructuring in closure and probably some sugar to allow free function of list of arguments when function of one tuple is required. But I don't see how we can revisit(and I believe we shouldn't) a number of actively discussed(!) and accepted proposals and dramatically change direction of Swift evolution even because of "lacks in terms of user ergonomics" for some period.

Btw, please also note that this will not be possible in Swift 4:
func f() {}
let c : ()->() = { v in print(v) }
c() // prints ()
f(print("fdsdf")) // executes print and then f()
, as function declared without parameters now can't accept even Void argument, function parenthesis is not empty tuple, as was allowed in Swift 3. Now you'll have these errors:

error: contextual closure type '() -> ()' expects 0 arguments, but 1 was used in closure body
let c : ()->() = { v in print(v) }
                    ^
error: argument passed to call that takes no arguments
f(print("fdsdf"))
   ^~~~~~~~~~~~~~

Probably this "feature" also was used in Swift 3 code, do we need to revisit it also? (I believe no)

···

On 07.06.2017 14:18, Gwendal Roué via swift-evolution wrote:

Gwendal

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

IMO, if tuples and argument lists are to be distinguished in any way, it is imperative that f3(+) and f4(+), and some of your other examples, _not_ work.

After all, if a tuple is not an argument list, it should possible to have a function of type ((Int, Int)) -> Int and a function of type (Int, Int) -> Int share the same name (IIUC, it’s a known bug that this does not currently work). Quite simply, there’s a type mismatch if you pass sum1 to f3–what happens if there’s a distinct, overloaded sum1 that takes a single tuple?

Chris’s suggestion restores a syntactic convenience without touching the type system. What you are arguing for is essentially making ((Int, Int)) -> Int and (Int, Int) -> Int synonymous again, either in some or in all contexts.

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

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

    Le 7 juin 2017 à 12:03, Adrian Zubarev via swift-evolution >>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

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

    Despite Chris Lattern being a semi-god, his double-parenthesis suggestion
    cruelly lacks in terms of user ergonomics. The compiler should be able to deal
    with the following code snippet, just like Swift 3 does:

    // two arguments
    funcf1(_closure: (Int, Int) -> Int) { closure(1, 2) }
    [...]

    Here is the full extent of the remarquable Swift 3 ergonomics. This full
    snippet compiles in Swift 3:

    funcsum1(_lhs: Int, _rhs: Int) -> Int{ returnlhs + rhs }
    funcsum2(lhs: Int, rhs: Int) -> Int{ returnlhs + rhs }
    funcsum3(tuple: (Int, Int)) -> Int{ returntuple.0 + tuple.1 }
    funcsum4(tuple: (lhs: Int, rhs: Int)) -> Int{ returntuple.lhs + tuple.rhs }

    // two arguments
    funcf1(_closure: (Int, Int) -> Int) { closure(1, 2) }
    f1{ lhs, rhs inlhs + rhs }
    f1{ (lhs, rhs) inlhs + rhs }
    f1{ tuple intuple.0 + tuple.1 }
    f1{ (tuple) intuple.0 + tuple.1 }
    f1(+)
        f1(sum1)
        f1(sum2)
        f1(sum3)
        f1(sum4)

    // two arguments, with documentation names: identical
    funcf2(_closure: (_a: Int, _b: Int) -> Int) { closure(1, 2) }
    f2{ lhs, rhs inlhs + rhs }
    f2{ (lhs, rhs) inlhs + rhs }
    f2{ tuple intuple.0 + tuple.1 }
    f2{ (tuple) intuple.0 + tuple.1 }
    f2(+)
        f2(sum1)
        f2(sum2)
        f2(sum3)
        f2(sum4)

    // one tuple argument
    funcf3(_closure: ((Int, Int)) -> Int) { closure((1, 2)) }
    f3{ lhs, rhs inlhs + rhs }
    f3{ (lhs, rhs) inlhs + rhs }
    f3{ tuple intuple.0 + tuple.1 }
    f3{ (tuple) intuple.0 + tuple.1 }
    f3(+)
        f3(sum1)
        f3(sum2)
        f3(sum3)
        f3(sum4)

    // one keyed tuple argument
    funcf4(_closure: ((a: Int, b: Int)) -> Int) { closure((a: 1, b: 2)) }
    f4{ lhs, rhs inlhs + rhs }
    f4{ (lhs, rhs) inlhs + rhs }
    f4{ tuple intuple.a + tuple.b }
    f4{ (tuple) intuple.a + tuple.b }
    f4(+)
        f4(sum1)
        f4(sum2)
        f4(sum3)
        f4(sum4)

    Gwendal

    _______________________________________________
    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

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

Overloading complicates this. Ignoring for a moment that we cannot declare the following due to Issues · apple/swift-issues · GitHub <Issues · apple/swift-issues · GitHub;

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 }

Mark

···

On Jun 8, 2017, at 2:05 PM, Vladimir.S via swift-evolution <swift-evolution@swift.org> 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> <mailto: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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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

I’m personally a fan of strictness and maximum consistency. I like the idea of flattening single element tuples down to the deepest thing contained that is not a single element tuple. So (x) flattens to x and ((((x,y)))) flattens to the tuple (x,y). Past SEs have dictated that you may not flatten a tuple down into a list of arguments.

For function types, it’s a rule that you wrap the arguments in an extra set of parentheses, so (Int)->Int maps one Int to one Int; (Int, Int)->Int maps two (separate) Ints to Int; ((Int,Int))->Int and ((((Int,Int))))->Int both map a 2-tuple of Ints to Int. Int->Int is obviously not allowed.

Now, for closures, I think we should just apply the same principles. Require parentheses for the arguments, which means double parentheses (or more) are needed when unwrapping a tuple.

In my view cognitive load is a big part of ergonomics. Reducing the rule set to a single principle seems very ergonomic to me.

P.S. Maybe the compiler should also ban unnecessarily nested tuples, for clarity. Just a thought.

···

On Jun 8, 2017, at 5:05 PM, Vladimir.S via swift-evolution <swift-evolution@swift.org> 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:

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

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

As a "regular user" I wholeheartedly agree with everything Robert just said. Syntactic consistency between function parameters and closure parameters is a big win for usability and discoverability.

I actually did not know that it was possible to destructure tuples in closure arguments before this discussion happened. If the syntax had been more explicit and consistent (parens required for arguments and tuples as described below and in previous emails) I could have discovered this great feature on my own a long time ago.

-Noah Desch

···

On Jun 8, 2017, at 5:39 PM, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:

Now, for closures, I think we should just apply the same principles. Require parentheses for the arguments, which means double parentheses (or more) are needed when unwrapping a tuple.

In my view cognitive load is a big part of ergonomics. Reducing the rule set to a single principle seems very ergonomic to me.

Me too, and I can also see what you mean regarding the "tone" of some of
the proposals. Tuple splat (or rather the whole "universal tuple concept")
would certainly have been an extremely powerful and elegant feature (I
really can't understand why they decided to call it "cute" in the
proposal), if it had worked all the way, without creating inconsistencies
and questions (inout, variadics etc).

However, I don't see any other way forward than to properly implement
SE-0110 et al, and then work from there.

/Jens

···

On Thu, Jun 8, 2017 at 7:20 PM, Víctor Pimentel Rodríguez via swift-evolution <swift-evolution@swift.org> wrote:

/../ I'm really going to miss being able to model every type of closure
with the type (T) -> U

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

Overloading complicates this. Ignoring for a moment that we cannot declare the following due to Issues · apple/swift-issues · GitHub

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 }

Good point! In this case, as I understand, this solution can't be considered as possible and we have(at this moment) only these possible directions:

1. Do not introduce any new syntax/sugar for this migration problem for now. Discuss/implement tuple destructuring / closures syntax after Swift 4 release.

2. Introduce new syntax for tuple argument destructuring in closure :
{((x,y)) in ..}
, this looks like good solution but not sure if it is the best and if this can block some future improvements here.

3. Introduce new 'let' syntax for tuple argument destructuring in closure :
{ let (x, y) in }
, also the same questions as for (2)

4. Allow type inference for tuple parts in already allowed syntax i.e. :
columns.index { (column: (name, _)) in column.name.lowercased() == lowercaseName }
, this will reduce the problem of migration and will not block any new improvements we can have in this area later, as this solution looks just as adding consistency for closure syntax: apply the same rule of type annotations but also for tuple parts, not just for arguments.
(In case there is no some problems with this proposal)

Do you see any other possible solution ?

···

On 09.06.2017 0:31, Mark Lacey wrote:

On Jun 8, 2017, at 2:05 PM, Vladimir.S via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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><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> >>>>> <mailto:swift-evolution@swift.org>> wrote:

Mark

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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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

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?

···

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

Xiaodi, Adrian, you are actively pushing so that something that was
allowed, well compiled (no runtime issue), and covered actual uses cases,
becomes forbidden. Without any developer advantage that would somehow
balance the change.
That's called a regression.
And what's the rationale, already? A sense of compiler aesthetics? Since
when a sense of compiler aesthetics is more valuable than a sense of code
aesthetics? Aren't both supposed to walk together as a pair?

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?

We can discuss what sugar we can have to have tuple destructuring in
closure and probably some sugar to allow free function of list of arguments
when function of one tuple is required. But I don't see how we can
revisit(and I believe we shouldn't) a number of actively discussed(!) and
accepted proposals and dramatically change direction of Swift evolution
even because of "lacks in terms of user ergonomics" for some period.

I don't know. Nobody seemed to care about regressions, so I felt like it
was high time some light was shed on them.

Btw, please also note that this will not be possible in Swift 4:
Probably this "feature" also was used in Swift 3 code, do we need to
revisit it also? (I believe no)

Writing "feature" with ironic quotes is a bad attitude. Some "features"
are the quality of life of developers.

When proposals are accepted without user feedback, it's reasonable to
expect that users come back after the harm has been done. It has happened
before, for fileprivate (SE-0025).

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.

This is the case now. Some real bad harm to the language ergonomics has

···

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

Le 7 juin 2017 à 14:42, Vladimir.S <svabox@gmail.com> a écrit :
On 07.06.2017 14:18, Gwendal Roué via swift-evolution wrote:
been done.

Again, I'm not talking about inner compiler design: feel free to do
whatever is reasonable for Swift and the community. But regressions.

Gwendal

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

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 }

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

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.

See also messages from Stephen Cellis, who shows how other kinds of

···

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

Le 7 juin 2017 à 15:11, Xiaodi Wu <xiaodi.wu@gmail.com> a écrit :
developer code has lost expressivity and clarity with those changes that
have been "considered and approved".

Cheers,
Gwendal

Let’s clarify: you just wrote that you have problems with SE-0029, SE-0066,
SE-0110, and “before,” did you not? In addition, Stephen has just said he
disagrees with SE-0155 also, and I’ve outlined how arguments as tuples
impacts SE-0046. That’s five or more proposals by my count–so I should
apologize, it’s only four or more other proposals, not five or six.

···

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

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.

That's wrong.

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

Then please push for the bug to be fixed. You are much better than am I at that. This does not mean breaking Swift 3 ergonomics.

The bug you mention involves type comparison, which is nowhere to be seen in the Swift 3 ergonomics checklist below. I'm sure we can all be happy.

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

    // two arguments
    func f1(_ closure: (Int, Int) -> Int) { closure(1, 2) }
    f1 { lhs, rhs in lhs + rhs }
    f1 { (lhs, rhs) in lhs + rhs }
    f1 { tuple in tuple.0 + tuple.1 }
    f1 { (tuple) in tuple.0 + tuple.1 }
    f1(+)
    f1(sum1)
    f1(sum2)
    f1(sum3)
    f1(sum4)
    
    // two arguments, with documentation names
    func f2(_ closure: (_ a: Int, _ b: Int) -> Int) { closure(1, 2) }
    f2 { lhs, rhs in lhs + rhs }
    f2 { (lhs, rhs) in lhs + rhs }
    f2 { tuple in tuple.0 + tuple.1 }
    f2 { (tuple) in tuple.0 + tuple.1 }
    f2(+)
    f2(sum1)
    f2(sum2)
    f2(sum3)
    f2(sum4)
    
    // one tuple argument
    func f3(_ closure: ((Int, Int)) -> Int) { closure((1, 2)) }
    f3 { lhs, rhs in lhs + rhs }
    f3 { (lhs, rhs) in lhs + rhs }
    f3 { tuple in tuple.0 + tuple.1 }
    f3 { (tuple) in tuple.0 + tuple.1 }
    f3(+)
    f3(sum1)
    f3(sum2)
    f3(sum3)
    f3(sum4)
    
    // one keyed tuple argument
    func f4(_ closure: ((a: Int, b: Int)) -> Int) { closure((a: 1, b: 2)) }
    f4 { lhs, rhs in lhs + rhs }
    f4 { (lhs, rhs) in lhs + rhs }
    f4 { tuple in tuple.a + tuple.b }
    f4 { (tuple) in tuple.a + tuple.b }
    f4(+)
    f4(sum1)
    f4(sum2)
    f4(sum3)
    f4(sum4)

Gwendal

···

Le 7 juin 2017 à 13:28, Adrian Zubarev <adrian.zubarev@devandartist.com> a écrit :

Xiaodi, Adrian, you are actively pushing so that something that was allowed, well compiled (no runtime issue), and covered actual uses cases, becomes forbidden. Without any developer advantage that would somehow balance the change.

That's called a regression.

func foo(_: (Int, Int)) {}
func bar(_: Int, _: Int) {}

type(of: foo) == type(of: bar) //=> true - It's a BUG!

Xiaodi, Adrian, you are actively pushing so that something that was allowed, well compiled (no runtime issue), and covered actual uses cases, becomes forbidden. Without any developer advantage that would somehow balance the change.
That's called a regression.
And what's the rationale, already? A sense of compiler aesthetics? Since when a sense of compiler aesthetics is more valuable than a sense of code aesthetics? Aren't both supposed to walk together as a pair?

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?

We can discuss what sugar we can have to have tuple destructuring in closure and probably some sugar to allow free function of list of arguments when function of one tuple is required. But I don't see how we can revisit(and I believe we shouldn't) a number of actively discussed(!) and accepted proposals and dramatically change direction of Swift evolution even because of "lacks in terms of user ergonomics" for some period.

I don't know. Nobody seemed to care about regressions, so I felt like it was high time some light was shed on them.

Btw, please also note that this will not be possible in Swift 4:
Probably this "feature" also was used in Swift 3 code, do we need to revisit it also? (I believe no)

Writing "feature" with ironic quotes is a bad attitude. Some "features" are the quality of life of developers.

When proposals are accepted without user feedback, it's reasonable to expect that users come back after the harm has been done. It has happened before, for fileprivate (SE-0025). This is the case now. Some real bad harm to the language ergonomics has been done.

Again, I'm not talking about inner compiler design: feel free to do whatever is reasonable for Swift and the community. But regressions.

Gwendal

···

Le 7 juin 2017 à 14:42, Vladimir.S <svabox@gmail.com> a écrit :
On 07.06.2017 14:18, Gwendal Roué via swift-evolution wrote:

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 }

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

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

Cheers,
Gwendal

···

Le 7 juin 2017 à 15:11, Xiaodi Wu <xiaodi.wu@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.