Proposal: Always flatten the single element tuple


#1

Introduction

Because the painful of SE-0110, here is a proposal to clarify the tuple
syntax.

Proposed solution
1. single element tuple always be flattened

let tuple1: (((Int))) = 0 // TypeOf(tuple1) == Int

let tuple2: ((((Int))), Int) = (0, 0) // TypeOf(tuple2) == (Int, Int)

2. function arguments list also consider as a tuple, which means the
function that accept a single tuple should always be flattened.

let fn1: (Int, Int) -> Void = { _, _ in }

let fn2: ((Int, Int)) -> Void = { _, _ in } // always flattened

let fn3: (Int, Int) -> Void = { _ in } // not allowed, here are two
arguments

let fn4: ((Int, Int)) -> Void = { _ in } // not allowed, here are two
arguments


#2

I like this a lot, but how do we solve for the function case?

    func add(_ x: Int, _ y: Int) -> Int {
      return x + y
    }
    [(1, 2)].map(add)

Where does `map` with a function of `((Int, Int)) -> Int` fit in?

···

On Jun 6, 2017, at 10:15 PM, Susan Cheng via swift-evolution <swift-evolution@swift.org> wrote:

Introduction

Because the painful of SE-0110, here is a proposal to clarify the tuple syntax.

Proposed solution

1. single element tuple always be flattened

let tuple1: (((Int))) = 0 // TypeOf(tuple1) == Int

let tuple2: ((((Int))), Int) = (0, 0) // TypeOf(tuple2) == (Int, Int)

2. function arguments list also consider as a tuple, which means the function that accept a single tuple should always be flattened.

let fn1: (Int, Int) -> Void = { _, _ in }

let fn2: ((Int, Int)) -> Void = { _, _ in } // always flattened

let fn3: (Int, Int) -> Void = { _ in } // not allowed, here are two arguments

let fn4: ((Int, Int)) -> Void = { _ in } // not allowed, here are two arguments

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


#3

[(1, 2)].map({ x, y in x + y }) // this line is correct
[(1, 2)].map({ tuple in tuple.0 + tuple.1 }) // this line should not
accepted

or

[(1, 2)].map({ $0 + $1 }) // this line is correct
[(1, 2)].map({ $0.0 + $0.1 }) // this line should not accepted

it's because `((Int, Int)) -> Int` always flatten to `(Int, Int) -> Int`
so, it should only accept the function with two arguments

···

2017-06-07 11:07 GMT+08:00 Stephen Celis <stephen.celis@gmail.com>:

I like this a lot, but how do we solve for the function case?

    func add(_ x: Int, _ y: Int) -> Int {
      return x + y
    }
    [(1, 2)].map(add)

Where does `map` with a function of `((Int, Int)) -> Int` fit in?

> On Jun 6, 2017, at 10:15 PM, Susan Cheng via swift-evolution < > swift-evolution@swift.org> wrote:
>
> Introduction
>
>
> Because the painful of SE-0110, here is a proposal to clarify the tuple
syntax.
>
> Proposed solution
>
> 1. single element tuple always be flattened
>
> let tuple1: (((Int))) = 0 // TypeOf(tuple1) == Int
>
> let tuple2: ((((Int))), Int) = (0, 0) // TypeOf(tuple2) == (Int, Int)
>
> 2. function arguments list also consider as a tuple, which means the
function that accept a single tuple should always be flattened.
>
> let fn1: (Int, Int) -> Void = { _, _ in }
>
> let fn2: ((Int, Int)) -> Void = { _, _ in } // always flattened
>
> let fn3: (Int, Int) -> Void = { _ in } // not allowed, here are two
arguments
>
> let fn4: ((Int, Int)) -> Void = { _ in } // not allowed, here are two
arguments
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution


#4

a PR is create: https://github.com/apple/swift-evolution/pull/722

···

2017-06-07 10:15 GMT+08:00 Susan Cheng <susan.doggie@gmail.com>:

Introduction

Because the painful of SE-0110, here is a proposal to clarify the tuple
syntax.

Proposed solution
1. single element tuple always be flattened

let tuple1: (((Int))) = 0 // TypeOf(tuple1) == Int

let tuple2: ((((Int))), Int) = (0, 0) // TypeOf(tuple2) == (Int, Int)

2. function arguments list also consider as a tuple, which means the
function that accept a single tuple should always be flattened.

let fn1: (Int, Int) -> Void = { _, _ in }

let fn2: ((Int, Int)) -> Void = { _, _ in } // always flattened

let fn3: (Int, Int) -> Void = { _ in } // not allowed, here are two
arguments

let fn4: ((Int, Int)) -> Void = { _ in } // not allowed, here are two
arguments


(Adrian Zubarev) #5

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 }

That’s a correct error:

let fn3: (Int, Int) -> Void = { _ in }
This should be allowed, because we might want to work with the whole tuple and not a desctructured elements only:

let fn4: ((Int, Int)) -> Void = { tuple in }

···

--
Adrian Zubarev
Sent with Airmail


#6

Just a thought

if parentheses is important, why the tuples are not?

var tuple1: (Int, Int) = (0, 0)

var tuple2: ((((Int, Int)))) = (0, 0)

type(of: tuple1) == type(of: tuple2) // true

var void: ((((((())))))) = ()

type(of: void) == type(of: Void()) // true

···

2017-06-07 10:15 GMT+08:00 Susan Cheng <susan.doggie@gmail.com>:

Introduction

Because the painful of SE-0110, here is a proposal to clarify the tuple
syntax.

Proposed solution
1. single element tuple always be flattened

let tuple1: (((Int))) = 0 // TypeOf(tuple1) == Int

let tuple2: ((((Int))), Int) = (0, 0) // TypeOf(tuple2) == (Int, Int)

2. function arguments list also consider as a tuple, which means the
function that accept a single tuple should always be flattened.

let fn1: (Int, Int) -> Void = { _, _ in }

let fn2: ((Int, Int)) -> Void = { _, _ in } // always flattened

let fn3: (Int, Int) -> Void = { _ in } // not allowed, here are two
arguments

let fn4: ((Int, Int)) -> Void = { _ in } // not allowed, here are two
arguments


#7

The inline cases make sense to me, but my concern for ambiguity are because we can define both of these functions:

    func add1(_ x: Int, _ y: Int) -> Int {
      return x + y
    }
    func add2(_ pair: (Int, Int)) -> Int {
      return pair.0 + pair.1
    }

    // What's the behavior here?
    [(1, 2)].map(add1)
    [(1, 2)].map(add2)

What comes to mind, though, is, Swift already prevents single-element tuples, so why not prevent functions that take a single tuple as the only argument?

Swift could provide a fix-it for functions that take single tuples and say: "Swift functions cannot take a single tuple. Please write a function that takes as many arguments as the tuple specified."

Considering the fact that Swift generally operates in a multi-argument world, and given that functions taking a single tuple are the minority, I think this would be a good way to move forward.

Stephen

···

On Jun 6, 2017, at 11:21 PM, Susan Cheng <susan.doggie@gmail.com> wrote:

[(1, 2)].map({ x, y in x + y }) // this line is correct
[(1, 2)].map({ tuple in tuple.0 + tuple.1 }) // this line should not accepted

or

[(1, 2)].map({ $0 + $1 }) // this line is correct
[(1, 2)].map({ $0.0 + $0.1 }) // this line should not accepted

it's because `((Int, Int)) -> Int` always flatten to `(Int, Int) -> Int`
so, it should only accept the function with two arguments

2017-06-07 11:07 GMT+08:00 Stephen Celis <stephen.celis@gmail.com>:
I like this a lot, but how do we solve for the function case?

    func add(_ x: Int, _ y: Int) -> Int {
      return x + y
    }
    [(1, 2)].map(add)

Where does `map` with a function of `((Int, Int)) -> Int` fit in?

> On Jun 6, 2017, at 10:15 PM, Susan Cheng via swift-evolution <swift-evolution@swift.org> wrote:
>
> Introduction
>
>
> Because the painful of SE-0110, here is a proposal to clarify the tuple syntax.
>
> Proposed solution
>
> 1. single element tuple always be flattened
>
> let tuple1: (((Int))) = 0 // TypeOf(tuple1) == Int
>
> let tuple2: ((((Int))), Int) = (0, 0) // TypeOf(tuple2) == (Int, Int)
>
> 2. function arguments list also consider as a tuple, which means the function that accept a single tuple should always be flattened.
>
> let fn1: (Int, Int) -> Void = { _, _ in }
>
> let fn2: ((Int, Int)) -> Void = { _, _ in } // always flattened
>
> let fn3: (Int, Int) -> Void = { _ in } // not allowed, here are two arguments
>
> let fn4: ((Int, Int)) -> Void = { _ in } // not allowed, here are two arguments
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution


#8

it's also clear that these code will never be ambiguous

[(1, 2)].map({ $0 }) // $0 always not a tuple, because arguments should be
flattened

···

2017-06-07 11:21 GMT+08:00 Susan Cheng <susan.doggie@gmail.com>:

[(1, 2)].map({ x, y in x + y }) // this line is correct
[(1, 2)].map({ tuple in tuple.0 + tuple.1 }) // this line should not
accepted

or

[(1, 2)].map({ $0 + $1 }) // this line is correct
[(1, 2)].map({ $0.0 + $0.1 }) // this line should not accepted

it's because `((Int, Int)) -> Int` always flatten to `(Int, Int) -> Int`
so, it should only accept the function with two arguments

2017-06-07 11:07 GMT+08:00 Stephen Celis <stephen.celis@gmail.com>:

I like this a lot, but how do we solve for the function case?

    func add(_ x: Int, _ y: Int) -> Int {
      return x + y
    }
    [(1, 2)].map(add)

Where does `map` with a function of `((Int, Int)) -> Int` fit in?

> On Jun 6, 2017, at 10:15 PM, Susan Cheng via swift-evolution < >> swift-evolution@swift.org> wrote:
>
> Introduction
>
>
> Because the painful of SE-0110, here is a proposal to clarify the tuple
syntax.
>
> Proposed solution
>
> 1. single element tuple always be flattened
>
> let tuple1: (((Int))) = 0 // TypeOf(tuple1) == Int
>
> let tuple2: ((((Int))), Int) = (0, 0) // TypeOf(tuple2) == (Int, Int)
>
> 2. function arguments list also consider as a tuple, which means the
function that accept a single tuple should always be flattened.
>
> let fn1: (Int, Int) -> Void = { _, _ in }
>
> let fn2: ((Int, Int)) -> Void = { _, _ in } // always flattened
>
> let fn3: (Int, Int) -> Void = { _ in } // not allowed, here are two
arguments
>
> let fn4: ((Int, Int)) -> Void = { _ in } // not allowed, here are two
arguments
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #9

Keep in mind there is also SE‚Äď0111 cometary which promises sugar for parameter labels for closures:

// **
let foo(tuple:): ((Int, Int)) -> Void

// Sugar for **
let foo: (tuple: (Int, Int)) -> Void
What will happen if you’d always flatten here?

···

--
Adrian Zubarev
Sent with Airmail

Am 7. Juni 2017 um 12:03:08, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

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 }

That’s a correct error:

let fn3: (Int, Int) -> Void = { _ in }
This should be allowed, because we might want to work with the whole tuple and not a desctructured elements only:

let fn4: ((Int, Int)) -> Void = { tuple in }

--
Adrian Zubarev
Sent with Airmail


#10

func add2(_ pair: (Int, Int)) -> Int {
    return pair.0 + pair.1
}

consider the follows

let _add2 = add2 // add2 have the typeof `((Int, Int)) -> Int`, it
should flatten to `(Int, Int) -> Int`

so these two lines are also acceptable
[(1, 2)].map(add1)
[(1, 2)].map(add2)

this proposal is not just changing the behaviour of closure, this proposal
also changing the tuple type

(((Int, Int))) flatten to (Int, Int)
(Int, (((Int, Int))), Int) flatten to (Int, (Int, Int), Int)

···

2017-06-07 12:03 GMT+08:00 Stephen Celis <stephen.celis@gmail.com>:

The inline cases make sense to me, but my concern for ambiguity are
because we can define both of these functions:

    func add1(_ x: Int, _ y: Int) -> Int {
      return x + y
    }
    func add2(_ pair: (Int, Int)) -> Int {
      return pair.0 + pair.1
    }

    // What's the behavior here?
    [(1, 2)].map(add1)
    [(1, 2)].map(add2)

What comes to mind, though, is, Swift already prevents single-element
tuples, so why not prevent functions that take a single tuple as the only
argument?

Swift could provide a fix-it for functions that take single tuples and
say: "Swift functions cannot take a single tuple. Please write a function
that takes as many arguments as the tuple specified."

Considering the fact that Swift generally operates in a multi-argument
world, and given that functions taking a single tuple are the minority, I
think this would be a good way to move forward.

Stephen

> On Jun 6, 2017, at 11:21 PM, Susan Cheng <susan.doggie@gmail.com> wrote:
>
> [(1, 2)].map({ x, y in x + y }) // this line is correct
> [(1, 2)].map({ tuple in tuple.0 + tuple.1 }) // this line should not
accepted
>
> or
>
> [(1, 2)].map({ $0 + $1 }) // this line is correct
> [(1, 2)].map({ $0.0 + $0.1 }) // this line should not accepted
>
> it's because `((Int, Int)) -> Int` always flatten to `(Int, Int) -> Int`
> so, it should only accept the function with two arguments
>
>
>
> 2017-06-07 11:07 GMT+08:00 Stephen Celis <stephen.celis@gmail.com>:
> I like this a lot, but how do we solve for the function case?
>
> func add(_ x: Int, _ y: Int) -> Int {
> return x + y
> }
> [(1, 2)].map(add)
>
> Where does `map` with a function of `((Int, Int)) -> Int` fit in?
>
> > On Jun 6, 2017, at 10:15 PM, Susan Cheng via swift-evolution < > swift-evolution@swift.org> wrote:
> >
> > Introduction
> >
> >
> > Because the painful of SE-0110, here is a proposal to clarify the
tuple syntax.
> >
> > Proposed solution
> >
> > 1. single element tuple always be flattened
> >
> > let tuple1: (((Int))) = 0 // TypeOf(tuple1) == Int
> >
> > let tuple2: ((((Int))), Int) = (0, 0) // TypeOf(tuple2) == (Int, Int)
> >
> > 2. function arguments list also consider as a tuple, which means the
function that accept a single tuple should always be flattened.
> >
> > let fn1: (Int, Int) -> Void = { _, _ in }
> >
> > let fn2: ((Int, Int)) -> Void = { _, _ in } // always flattened
> >
> > let fn3: (Int, Int) -> Void = { _ in } // not allowed, here are two
arguments
> >
> > let fn4: ((Int, Int)) -> Void = { _ in } // not allowed, here are two
arguments
> >
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution@swift.org
> > https://lists.swift.org/mailman/listinfo/swift-evolution
>
>


(Gwendal Roué) #11

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) }
    f1 { lhs, rhs in lhs + rhs }
    f1 { (lhs, rhs) in lhs + rhs }
    f1 { tuple in tuple.0 + tuple.1 }
    f1(+)
    
    // 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(+)
    
    // 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(+)
    
    // 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(+)

This covers the Swift 3 regressions developped by Stephen Celis and I, unless I have missed any. And this should be the goal of the designers of this language, *regardless of the actual types*. Developers are olding their breath: please just make this happen.

Now Swift 3 may have an issue with $n parameters. Here is my suggestion, should `((Int, Int)) -> Int` be different from `(Int, Int) -> Int`:

    f1 { $0 + $1 } // OK
    f1 { $0.0 + $0.1 } // OK in Swift 3, compiler error in Swift 4?
    
    f2 { $0 + $1 } // OK
    f2 { $0.0 + $0.1 } // OK in Swift 3, compiler error in Swift 4?
    
    f3 { $0 + $1 } // OK in Swift 3, compiler error in Swift 4?
    f3 { $0.0 + $0.1 } // OK
    
    f4 { $0 + $1 } // OK in Swift 3, compiler error in Swift 4?
    f4 { $0.a + $0.b } // OK

Gwendal Roué

···

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 }


#12

Replacement for fn4 is just make a tuple inside the closure

let workWithTuple: (Int, Int) -> Void = { doSomething(withTuple: ($0, $1)) }

···

2017-06-07 18:03 GMT+08:00 Adrian Zubarev <adrian.zubarev@devandartist.com>:

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 }

That’s a correct error:

let fn3: (Int, Int) -> Void = { _ in }

This should be allowed, because we might want to work with the whole tuple
and not a desctructured elements only:

let fn4: ((Int, Int)) -> Void = { tuple in }

--
Adrian Zubarev
Sent with Airmail


(Xiaodi Wu) #13

Just a thought

if parentheses is important, why the tuples are not?

It is not parentheses that are important; it is the distinction between an
argument list and a tuple. They both happen to be written with parentheses.

var tuple1: (Int, Int) = (0, 0)

···

On Wed, Jun 7, 2017 at 10:15 PM, Susan Cheng via swift-evolution < swift-evolution@swift.org> wrote:

var tuple2: ((((Int, Int)))) = (0, 0)

type(of: tuple1) == type(of: tuple2) // true

var void: ((((((())))))) = ()

type(of: void) == type(of: Void()) // true

2017-06-07 10:15 GMT+08:00 Susan Cheng <susan.doggie@gmail.com>:

Introduction

Because the painful of SE-0110, here is a proposal to clarify the tuple
syntax.

Proposed solution
1. single element tuple always be flattened

let tuple1: (((Int))) = 0 // TypeOf(tuple1) == Int

let tuple2: ((((Int))), Int) = (0, 0) // TypeOf(tuple2) == (Int, Int)

2. function arguments list also consider as a tuple, which means the
function that accept a single tuple should always be flattened.

let fn1: (Int, Int) -> Void = { _, _ in }

let fn2: ((Int, Int)) -> Void = { _, _ in } // always flattened

let fn3: (Int, Int) -> Void = { _ in } // not allowed, here are two
arguments

let fn4: ((Int, Int)) -> Void = { _ in } // not allowed, here are two
arguments

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


(Gwendal Roué) #14

I think is is because Swift doesn't have tuples with a single value: those parenthesis are just parenthesis around an expression:

    let a = 1 + 2
    let b = (1 + 2)
    let c = (1 + 2) * 3
    let d = ((1 + 2)) * 3

Many languages behave like that, Swift is no exception.

It also allows some fancy/legacy/foreign programming styles :slight_smile:

    // C-style if
    if (a && b) {
        ...
    }
    // "return function"
    return(a && b)

Languages that have single-valued tuples need a special syntax so that they are distinguished from parenthesised expressions. In Python, this is a trailing comma:

    1 # 1
    (1) # 1
    (1,) # (1,)

Swift currently disallows trailing commas inside parenthesis.

Gwendal

···

Le 8 juin 2017 à 05:15, Susan Cheng via swift-evolution <swift-evolution@swift.org> a écrit :

Just a thought

if parentheses is important, why the tuples are not?

var tuple1: (Int, Int) = (0, 0)
var tuple2: ((((Int, Int)))) = (0, 0)

type(of: tuple1) == type(of: tuple2) // true

var void: ((((((())))))) = ()

type(of: void) == type(of: Void()) // true


(Brent Royal-Gordon) #15

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.

···

On Jun 7, 2017, at 3:03 AM, Adrian Zubarev via swift-evolution <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 }

--
Brent Royal-Gordon
Architechies


(Víctor Pimentel) #16

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

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

···

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

Just a thought

if parentheses is important, why the tuples are not?

   -

   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/proposals/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 :stuck_out_tongue:

--
Víctor Pimentel


#17

if it's necessary to distinct argument list and tuple, what about the enum?

enum Foo {

    case tuple(((((a: Int, b: Int)))))

}

let tuple = Foo.tuple((1, 2))

if case let .tuple(a, b) = tuple {

    (a, b) // (1, 2)

}

if case let .tuple(tuple) = tuple {

    tuple // (1, 2)

}

···

2017-06-08 11:36 GMT+08:00 Xiaodi Wu <xiaodi.wu@gmail.com>:

On Wed, Jun 7, 2017 at 10:15 PM, Susan Cheng via swift-evolution < > swift-evolution@swift.org> wrote:

Just a thought

if parentheses is important, why the tuples are not?

It is not parentheses that are important; it is the distinction between an
argument list and a tuple. They both happen to be written with parentheses.

var tuple1: (Int, Int) = (0, 0)

var tuple2: ((((Int, Int)))) = (0, 0)

type(of: tuple1) == type(of: tuple2) // true

var void: ((((((())))))) = ()

type(of: void) == type(of: Void()) // true

2017-06-07 10:15 GMT+08:00 Susan Cheng <susan.doggie@gmail.com>:

Introduction

Because the painful of SE-0110, here is a proposal to clarify the tuple
syntax.

Proposed solution
1. single element tuple always be flattened

let tuple1: (((Int))) = 0 // TypeOf(tuple1) == Int

let tuple2: ((((Int))), Int) = (0, 0) // TypeOf(tuple2) == (Int, Int)

2. function arguments list also consider as a tuple, which means the
function that accept a single tuple should always be flattened.

let fn1: (Int, Int) -> Void = { _, _ in }

let fn2: ((Int, Int)) -> Void = { _, _ in } // always flattened

let fn3: (Int, Int) -> Void = { _ in } // not allowed, here are two
arguments

let fn4: ((Int, Int)) -> Void = { _ in } // not allowed, here are two
arguments

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


#18

that makes sense to me :stuck_out_tongue_winking_eye:

···

2017-06-08 12:07 GMT+08:00 Gwendal Roué <gwendal.roue@gmail.com>:

Le 8 juin 2017 à 05:15, Susan Cheng via swift-evolution < > swift-evolution@swift.org> a écrit :

Just a thought

if parentheses is important, why the tuples are not?

var tuple1: (Int, Int) = (0, 0)
var tuple2: ((((Int, Int)))) = (0, 0)

type(of: tuple1) == type(of: tuple2) // true

var void: ((((((())))))) = ()

type(of: void) == type(of: Void()) // true

I think is is because Swift doesn't have tuples with a single value: those
parenthesis are just parenthesis around an expression:

    let a = 1 + 2
    let b = (1 + 2)
    let c = (1 + 2) * 3
    let d = ((1 + 2)) * 3

Many languages behave like that, Swift is no exception.

It also allows some fancy/legacy/foreign programming styles :slight_smile:

    // C-style if
    if (a && b) {
        ...
    }
    // "return function"
    return(a && b)

Languages that have single-valued tuples need a special syntax so that
they are distinguished from parenthesised expressions. In Python, this is a
trailing comma:

    1 # 1
    (1) # 1
    (1,) # (1,)

Swift currently disallows trailing commas inside parenthesis.

Gwendal


#19

As one of many who is directly affected and wants resolution, I think the discussion of re-evaluating SE-0110 most certainly expands to these earlier decisions. While Swift has moved in the direction of distinguishing tuples and argument lists in decidedly different ways, I think it's perfectly valid to consider the alternative: a simpler and equally-valid solution: flattened tuples that are isomorphic to argument lists.

Argument labels certainly complicate semantics, but we still erase tuple labels in general use to this day. Meanwhile, prohibited arbitrary reordering remains complicated (I just recently worked with an SPM module that advertised instructions the compiler disallowed, and it took awhile to find the correct, compiler-allowed ordering while troubleshooting confusing, compiler-driven error messaging).

All of these hiccups are the result of compiler optimizations and simplifications that hinder end-user ergonomics.

Again: while I can appreciate making the compiler as simple and strict as possible, the end-user and end-use cases suffer along the way.

Stephen

···

On Jun 7, 2017, at 1:05 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

This is not what was meant during discussion about re-evaluating SE-0110. Tuples already behave as described, but function argument lists are not tuples and have not been for a very long time: see SE-0029, SE-0066.

Also, consider SE-0046, which makes possible labels in single-argument argument lists (not possible in tuples), and SE-0060, which prohibits arbitrary reordering (although still possible in tuples). This is to say that the whole direction of Swift since version 2 has been to erase the historical relationship between tuples and argument lists.

The question is how to accommodate some common use cases for destructuring as a matter of syntactic sugar after having carefully distinguished argument lists and tuples in the compiler, which is a given, not how to roll back a change that was settled in 2011, by Chris’s telling.


(Xiaodi Wu) #20

This is not what was meant during discussion about re-evaluating SE-0110.
Tuples already behave as described, but function argument lists are not
tuples and have not been for a very long time: see SE-0029, SE-0066.

Also, consider SE-0046, which makes possible labels in single-argument
argument lists (not possible in tuples), and SE-0060, which prohibits
arbitrary reordering (although still possible in tuples). This is to say
that the whole direction of Swift since version 2 has been to erase the
historical relationship between tuples and argument lists.

The question is how to accommodate some common use cases for destructuring
as a matter of syntactic sugar after having carefully distinguished
argument lists and tuples in the compiler, which is a given, not how to
roll back a change that was settled in 2011, by Chris’s telling.

···

On Tue, Jun 6, 2017 at 23:14 Susan Cheng via swift-evolution < swift-evolution@swift.org> wrote:

func add2(_ pair: (Int, Int)) -> Int {
    return pair.0 + pair.1
}

consider the follows

let _add2 = add2 // add2 have the typeof `((Int, Int)) -> Int`, it
should flatten to `(Int, Int) -> Int`

so these two lines are also acceptable
[(1, 2)].map(add1)
[(1, 2)].map(add2)

this proposal is not just changing the behaviour of closure, this proposal
also changing the tuple type

(((Int, Int))) flatten to (Int, Int)
(Int, (((Int, Int))), Int) flatten to (Int, (Int, Int), Int)

2017-06-07 12:03 GMT+08:00 Stephen Celis <stephen.celis@gmail.com>:

The inline cases make sense to me, but my concern for ambiguity are
because we can define both of these functions:

    func add1(_ x: Int, _ y: Int) -> Int {
      return x + y
    }
    func add2(_ pair: (Int, Int)) -> Int {
      return pair.0 + pair.1
    }

    // What's the behavior here?
    [(1, 2)].map(add1)
    [(1, 2)].map(add2)

What comes to mind, though, is, Swift already prevents single-element
tuples, so why not prevent functions that take a single tuple as the only
argument?

Swift could provide a fix-it for functions that take single tuples and
say: "Swift functions cannot take a single tuple. Please write a function
that takes as many arguments as the tuple specified."

Considering the fact that Swift generally operates in a multi-argument
world, and given that functions taking a single tuple are the minority, I
think this would be a good way to move forward.

Stephen

> On Jun 6, 2017, at 11:21 PM, Susan Cheng <susan.doggie@gmail.com> >> wrote:
>
> [(1, 2)].map({ x, y in x + y }) // this line is correct
> [(1, 2)].map({ tuple in tuple.0 + tuple.1 }) // this line should not
accepted
>
> or
>
> [(1, 2)].map({ $0 + $1 }) // this line is correct
> [(1, 2)].map({ $0.0 + $0.1 }) // this line should not accepted
>
> it's because `((Int, Int)) -> Int` always flatten to `(Int, Int) -> Int`
> so, it should only accept the function with two arguments
>
>
>
> 2017-06-07 11:07 GMT+08:00 Stephen Celis <stephen.celis@gmail.com>:
> I like this a lot, but how do we solve for the function case?
>
> func add(_ x: Int, _ y: Int) -> Int {
> return x + y
> }
> [(1, 2)].map(add)
>
> Where does `map` with a function of `((Int, Int)) -> Int` fit in?
>
> > On Jun 6, 2017, at 10:15 PM, Susan Cheng via swift-evolution < >> swift-evolution@swift.org> wrote:
> >
> > Introduction
> >
> >
> > Because the painful of SE-0110, here is a proposal to clarify the
tuple syntax.
> >
> > Proposed solution
> >
> > 1. single element tuple always be flattened
> >
> > let tuple1: (((Int))) = 0 // TypeOf(tuple1) == Int
> >
> > let tuple2: ((((Int))), Int) = (0, 0) // TypeOf(tuple2) == (Int, Int)
> >
> > 2. function arguments list also consider as a tuple, which means the
function that accept a single tuple should always be flattened.
> >
> > let fn1: (Int, Int) -> Void = { _, _ in }
> >
> > let fn2: ((Int, Int)) -> Void = { _, _ in } // always flattened
> >
> > let fn3: (Int, Int) -> Void = { _ in } // not allowed, here are two
arguments
> >
> > let fn4: ((Int, Int)) -> Void = { _ in } // not allowed, here are
two arguments
> >
> > _______________________________________________
> > 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