[Pitch] Tuple Destructuring in Parameter Lists


(Dennis Weissmann) #1

Following a short discussion with positive feedback on [swift-users](http://thread.gmane.org/gmane.comp.lang.swift.user/1812) I’d like to discuss the following:

Tuples should be destructible into their components in parameter lists.

Consider the following code:

let a = [0,1,2,3,4,5,6,7,8,9]
let b = [0,1,2,3,4,5,6,7,8,9]

let c = zip(a,b).reduce(0) { acc, tuple in
  acc + tuple.0 + tuple.1
}

tuple is of type (Int, Int).

The problem is that the calculation is not very comprehensible due to .0 and .1. That’s when destructuring tuples directly in the parameter list comes into play:

let c = zip(a,b).reduce(0) { acc, (valueA, valueB) in
  acc + valueA + valueB
}

The above is what I propose should be accepted by the compiler (but currently isn’t).

Currently tuple destructuring is possible like this:

let c = zip(a,b).reduce(0) { (acc, tuple) in
  let (valueA, valueB) = tuple
  return acc + valueA + valueB
}

This is not about saving one line ;-). I just find it much more intuitive to destructure the tuple in the parameter list itself.

The same thing could be done for functions:

func takesATuple(someInt: Int, tuple: (String, String))

Here we also need to destructure the tuple inside the function, but the intuitive place (at least for me) to do this would be the parameter list.

In the following example I'm making use of Swift’s feature to name parameters different from their labels (for internal use inside the function, this is not visible to consumers of the API):

func takesATuple(someInt: Int, tuple (valueA, valueB): (String, String))

Here valueA and valueB would be directly usable within the function. The tuple as a whole would not be available anymore.

Now it’s your turn!

1. What do you think?
2. Is this worth being discussed now (i.e. is it implementable in the Swift 3 timeframe) or should I delay it?

Cheers,

- Dennis


(TJ Usiyan) #2

+1
I have wanted this since the first beta. I hadn't proposed because I
haven't come up with a nice syntax to do this in functions/methods. I don't
particularly like
    func takesATuple(someInt: Int, tuple (valueA, valueB): (String, String))

and the closes that I have come is to simply reuse the closure syntax with

    func takesATuple(someInt: Int, tuple: (String, String)) {
(someInt, (valueA,
valueB)) in

but that gets confusing in my opinion, specifically if you choose to have
different names inside and outside.

···

On Thu, May 5, 2016 at 11:22 AM, Dennis Weissmann via swift-evolution < swift-evolution@swift.org> wrote:

Following a short discussion with positive feedback on [swift-users](
http://thread.gmane.org/gmane.comp.lang.swift.user/1812) I’d like to
discuss the following:

Tuples should be destructible into their components in parameter lists.

Consider the following code:

let a = [0,1,2,3,4,5,6,7,8,9]
let b = [0,1,2,3,4,5,6,7,8,9]

let c = zip(a,b).reduce(0) { acc, tuple in
  acc + tuple.0 + tuple.1
}

tuple is of type (Int, Int).

The problem is that the calculation is not very comprehensible due to .0
and .1. That’s when destructuring tuples directly in the parameter list
comes into play:

let c = zip(a,b).reduce(0) { acc, (valueA, valueB) in
  acc + valueA + valueB
}

The above is what I propose should be accepted by the compiler (but
currently isn’t).

Currently tuple destructuring is possible like this:

let c = zip(a,b).reduce(0) { (acc, tuple) in
  let (valueA, valueB) = tuple
  return acc + valueA + valueB
}

This is not about saving one line ;-). I just find it much more intuitive
to destructure the tuple in the parameter list itself.

The same thing could be done for functions:

func takesATuple(someInt: Int, tuple: (String, String))

Here we also need to destructure the tuple inside the function, but the
intuitive place (at least for me) to do this would be the parameter list.

In the following example I'm making use of Swift’s feature to name
parameters different from their labels (for internal use inside the
function, this is not visible to consumers of the API):

func takesATuple(someInt: Int, tuple (valueA, valueB): (String, String))

Here valueA and valueB would be directly usable within the function. The
tuple as a whole would not be available anymore.

Now it’s your turn!

1. What do you think?
2. Is this worth being discussed now (i.e. is it implementable in the
Swift 3 timeframe) or should I delay it?

Cheers,

- Dennis

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


(David Rönnqvist) #3

+1 I’ve tried to write parameter lists like `acc, (valueA, valueB) in` in the past, expecting it to work like this

···

On 05 May 2016, at 20:22, Dennis Weissmann via swift-evolution <swift-evolution@swift.org> wrote:

Following a short discussion with positive feedback on [swift-users](http://thread.gmane.org/gmane.comp.lang.swift.user/1812) I’d like to discuss the following:

Tuples should be destructible into their components in parameter lists.

Consider the following code:

let a = [0,1,2,3,4,5,6,7,8,9]
let b = [0,1,2,3,4,5,6,7,8,9]

let c = zip(a,b).reduce(0) { acc, tuple in
  acc + tuple.0 + tuple.1
}

tuple is of type (Int, Int).

The problem is that the calculation is not very comprehensible due to .0 and .1. That’s when destructuring tuples directly in the parameter list comes into play:

let c = zip(a,b).reduce(0) { acc, (valueA, valueB) in
  acc + valueA + valueB
}

The above is what I propose should be accepted by the compiler (but currently isn’t).

Currently tuple destructuring is possible like this:

let c = zip(a,b).reduce(0) { (acc, tuple) in
  let (valueA, valueB) = tuple
  return acc + valueA + valueB
}

This is not about saving one line ;-). I just find it much more intuitive to destructure the tuple in the parameter list itself.

The same thing could be done for functions:

func takesATuple(someInt: Int, tuple: (String, String))

Here we also need to destructure the tuple inside the function, but the intuitive place (at least for me) to do this would be the parameter list.

In the following example I'm making use of Swift’s feature to name parameters different from their labels (for internal use inside the function, this is not visible to consumers of the API):

func takesATuple(someInt: Int, tuple (valueA, valueB): (String, String))

Here valueA and valueB would be directly usable within the function. The tuple as a whole would not be available anymore.

Now it’s your turn!

1. What do you think?
2. Is this worth being discussed now (i.e. is it implementable in the Swift 3 timeframe) or should I delay it?

Cheers,

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


(Geordie J) #4

Comments below

Following a short discussion with positive feedback on [swift-users](http://thread.gmane.org/gmane.comp.lang.swift.user/1812) I’d like to discuss the following:

Tuples should be destructible into their components in parameter lists.

Consider the following code:

let a = [0,1,2,3,4,5,6,7,8,9]
let b = [0,1,2,3,4,5,6,7,8,9]

let c = zip(a,b).reduce(0) { acc, tuple in
  acc + tuple.0 + tuple.1
}

tuple is of type (Int, Int).

The problem is that the calculation is not very comprehensible due to .0 and .1. That’s when destructuring tuples directly in the parameter list comes into play:

let c = zip(a,b).reduce(0) { acc, (valueA, valueB) in
  acc + valueA + valueB
}

+1 I think this is a great way to go about it.

The above is what I propose should be accepted by the compiler (but currently isn’t).

Currently tuple destructuring is possible like this:

let c = zip(a,b).reduce(0) { (acc, tuple) in
  let (valueA, valueB) = tuple
  return acc + valueA + valueB
}

This is not about saving one line ;-). I just find it much more intuitive to destructure the tuple in the parameter list itself.

Agreed

The same thing could be done for functions:

func takesATuple(someInt: Int, tuple: (String, String))

Here we also need to destructure the tuple inside the function, but the intuitive place (at least for me) to do this would be the parameter list.

In the following example I'm making use of Swift’s feature to name parameters different from their labels (for internal use inside the function, this is not visible to consumers of the API):

func takesATuple(someInt: Int, tuple (valueA, valueB): (String, String))

I’m not such a fan of this though. I realize what I’m about to write here is discussing a slightly different point but bear with me: I was under the impression it was already possible to do something like this (maybe only possible with typealiases):

func takesATuple(someInt: Int, tuple: (valueA: String, valueB: String)) {}

I find that syntax readable and extensible: you can make a type alias for your tuple type '(valueA: String, valueB: String)‘, you can then use it like this:

func takesATuple(someInt: Int, tuple: MyAliasedTupleType) {
  print(tuple.valueA)
}

It’s true that you still have the ‚overhead‘ of having to type tuple. before accessing its members. But this is almost always what I want (hopefully you’d never actually name your tuple ‚tuple‘, instead it’d be a logical namespace for what it contains). Do you have a real-world example where you’d need this? To me it seems that in a case like this the API that produced the tuple would need refining rather than the language itself.

···

Am 05.05.2016 um 20:22 schrieb Dennis Weissmann via swift-evolution <swift-evolution@swift.org>:

Here valueA and valueB would be directly usable within the function. The tuple as a whole would not be available anymore.

Now it’s your turn!

1. What do you think?
2. Is this worth being discussed now (i.e. is it implementable in the Swift 3 timeframe) or should I delay it?

Cheers,

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


(Dennis Weissmann) #5

Thanks for all your feedback!

This is the current statistic:
Closure syntax: All positive
Function syntax: 3 (or 4) positive, 2 negative

I’ll try to address the concern Geordie and T.J. have.

func takesATuple(someInt: Int, tuple: (valueA: String, valueB: String)) {}

It’s true that you still have the ‚overhead‘ of having to type tuple. before accessing its members. But this is almost always what I want (hopefully you’d never actually name your tuple ‚tuple‘, instead it’d be a logical namespace for what it contains). Do you have a real-world example where you’d need this? To me it seems that in a case like this the API that produced the tuple would need refining rather than the language itself.

What you suggest here is not tuple destructuring but using labeled tuples. And while I’m totally with you that this is for many cases the better approach, I still think we should introduce it to functions as well, for consistency and readability reasons.
In the end inconsistency is what led to this thread because tuple destructuring is already possible today - in for loops:

let stringTuple = [("", "”), ("", "")]
for (i, j) in stringTuple {}

That made me wonder if it’s also possible for closures (because I needed it there - and eventually someone will definitely wonder if it’s possible for function arguments as well).

You also asked me for my use case. To be honest, I don’t have one for the function version, but imagine the following:

My current closure use case is this (template.points and resampledPoints are of type [CGPoint]):

let localHighestSimilarity = zip(template.points, resampledPoints).reduce(0.0) { accumulator, points in
  let (template, resampled) = points
  return accumulator + Double(template.x * resampled.x + template.y * resampled.y)
}

To reuse this code elsewhere I maybe want to refactor the closure into a function (using your labeled tuple suggestion):

func accumulateSimilarity(accumulator: Double, for points: (point1: CGPoint, point2: CGPoint)) -> Double {
  return accumulator + Double(points.point1.x * points.point2.x + points.point1.y * points.point2.y)
}

This isn’t particularity readable (image passing a CGRect and you need the points or a more complex calculation). Compare it to this:

func accumulateSimilarity(accumulator: Double, for (point1, point2): (CGPoint, CGPoint)) -> Double {
  return accumulator + Double(point1.x * point2.x + point1.y * point2.y)
}

You can of course still pass a named tuple instead of an unnamed, but it doesn’t make any difference, which brings me to an aside*.

I think the second approach makes the calculation much more comprehensible and it just feels “intuitive” (again, at least for me) :).

- Dennis

* I’m not sure how scientifically correct the following statement is but strictly speaking (at least for me) (valueA: String, valueB: String) is not of the same type as (String, String) just like func d(string: String, int: Int) is different from func e(_: String, _: Int) though in Swift the tuples are interchangeable (you can pass one where the other is expected).

···

On May 8, 2016, at 6:10 PM, Geordie J <geojay@gmail.com> wrote:

Comments below

Am 05.05.2016 um 20:22 schrieb Dennis Weissmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

Following a short discussion with positive feedback on [swift-users](http://thread.gmane.org/gmane.comp.lang.swift.user/1812) I’d like to discuss the following:

Tuples should be destructible into their components in parameter lists.

Consider the following code:

let a = [0,1,2,3,4,5,6,7,8,9]
let b = [0,1,2,3,4,5,6,7,8,9]

let c = zip(a,b).reduce(0) { acc, tuple in
  acc + tuple.0 + tuple.1
}

tuple is of type (Int, Int).

The problem is that the calculation is not very comprehensible due to .0 and .1. That’s when destructuring tuples directly in the parameter list comes into play:

let c = zip(a,b).reduce(0) { acc, (valueA, valueB) in
  acc + valueA + valueB
}

+1 I think this is a great way to go about it.

The above is what I propose should be accepted by the compiler (but currently isn’t).

Currently tuple destructuring is possible like this:

let c = zip(a,b).reduce(0) { (acc, tuple) in
  let (valueA, valueB) = tuple
  return acc + valueA + valueB
}

This is not about saving one line ;-). I just find it much more intuitive to destructure the tuple in the parameter list itself.

Agreed

The same thing could be done for functions:

func takesATuple(someInt: Int, tuple: (String, String))

Here we also need to destructure the tuple inside the function, but the intuitive place (at least for me) to do this would be the parameter list.

In the following example I'm making use of Swift’s feature to name parameters different from their labels (for internal use inside the function, this is not visible to consumers of the API):

func takesATuple(someInt: Int, tuple (valueA, valueB): (String, String))

I’m not such a fan of this though. I realize what I’m about to write here is discussing a slightly different point but bear with me: I was under the impression it was already possible to do something like this (maybe only possible with typealiases):

func takesATuple(someInt: Int, tuple: (valueA: String, valueB: String)) {}

I find that syntax readable and extensible: you can make a type alias for your tuple type '(valueA: String, valueB: String)‘, you can then use it like this:

func takesATuple(someInt: Int, tuple: MyAliasedTupleType) {
  print(tuple.valueA)
}

It’s true that you still have the ‚overhead‘ of having to type tuple. before accessing its members. But this is almost always what I want (hopefully you’d never actually name your tuple ‚tuple‘, instead it’d be a logical namespace for what it contains). Do you have a real-world example where you’d need this? To me it seems that in a case like this the API that produced the tuple would need refining rather than the language itself.

Here valueA and valueB would be directly usable within the function. The tuple as a whole would not be available anymore.

Now it’s your turn!

1. What do you think?
2. Is this worth being discussed now (i.e. is it implementable in the Swift 3 timeframe) or should I delay it?

Cheers,

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


(Dennis Weissmann) #6

Thanks for everyone participating in this discussion! :slight_smile:
I’ve drafted a formal proposal, it is available here: https://github.com/dennisweissmann/swift-evolution/blob/tuple-destructuring/proposals/0000-tuple-destructuring.md

Please let me know what you think (it would be great if a native speaker could take a look at grammar and spelling mistakes). Thanks!
Tuple Destructuring in Parameter Lists
Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
Author(s): Dennis Weissmann <https://github.com/dennisweissmann>
Status: Awaiting review <file:///Volumes/Data/Desktop/swift-evolution/proposals/0000-tuple-destructuring.md#rationale>
Review manager: TBD
Introduction
Tuple destructuring is the process of extracting elements from tuples.

This is valid today:

Swift
let point = (x: 20.0, y: 31.0, z: 42.0)
// Approach 1:
let x = point.x
let y = point.y
let z = point.z

// Approach 2:
let (x, y, z) = point

// For-in loops support tuple destructuring
for (x, y, z) in [point] {
  // use x, y, z
}
Swift-evolution thread: [Pitch] Tuple Destructuring in Parameter Lists <http://thread.gmane.org/gmane.comp.lang.swift.evolution/16190>
Motivation
This proposal seeks to generalize this behavior for every use case where tuples need to be destructured. These are parameter lists in closures and parameter lists in functions. Consistency is a major goal of Swift but it is currently only possible to destructure tuples in the above mentioned places.

Proposed solution
Extending tuple destructuring to parameter lists seems natural and improves consistency in the language.

Closures

Parameters in closures are currently not directly destructable. They can either be accessed via .0, .1, etc. or can be destructured by assigning them to variables in an explicit statement.

It feels natural to do this right in the parameter list itself (just like with for-in loops).

Swift
let a = [0,1,2,3,4,5,6,7,8,9]
let b = [0,1,2,3,4,5,6,7,8,9]

// Allowed today:
let c = zip(a,b).reduce(0) { acc, tuple in
  acc + tuple.0 + tuple.1
}

// Also allowed today:
let c = zip(a,b).reduce(0) { acc, tuple in
  let (valueA, valueB) = tuple
  return acc + valueA + valueB
}

// Proposed syntax:
let c = zip(a,b).reduce(0) { acc, (valueA, valueB) in
  acc + valueA + valueB
}
Functions

When it comes to functions this proposal uses Swift's feature of differentiating between internal and external parameter names.

Swift
// Allowed today:
func takesATuple(tuple: (Int, Int)) {
  let valueA = tuple.0
  let valueB = tuple.1
  // ...
}

// Proposed syntax:
func takesATuple(tuple (valueA, valueB): (Int, Int)) {
  // use valueA
  // use valueB
}
This design has no visible effects to the call site of a function but makes it very convenient for the function author to use the tuple's elements inside the function body.

Impact on existing code
This feature is strictly additive and does not effect current code.

Alternatives considered
Leave it as is destructure in a separate assignment.

- Dennis

···

On May 11, 2016, at 10:12 AM, Dennis Weissmann via swift-evolution <swift-evolution@swift.org> wrote:

Thanks for all your feedback!

This is the current statistic:
Closure syntax: All positive
Function syntax: 3 (or 4) positive, 2 negative

I’ll try to address the concern Geordie and T.J. have.

func takesATuple(someInt: Int, tuple: (valueA: String, valueB: String)) {}

It’s true that you still have the ‚overhead‘ of having to type tuple. before accessing its members. But this is almost always what I want (hopefully you’d never actually name your tuple ‚tuple‘, instead it’d be a logical namespace for what it contains). Do you have a real-world example where you’d need this? To me it seems that in a case like this the API that produced the tuple would need refining rather than the language itself.

What you suggest here is not tuple destructuring but using labeled tuples. And while I’m totally with you that this is for many cases the better approach, I still think we should introduce it to functions as well, for consistency and readability reasons.
In the end inconsistency is what led to this thread because tuple destructuring is already possible today - in for loops:

let stringTuple = [("", "”), ("", "")]
for (i, j) in stringTuple {}

That made me wonder if it’s also possible for closures (because I needed it there - and eventually someone will definitely wonder if it’s possible for function arguments as well).

You also asked me for my use case. To be honest, I don’t have one for the function version, but imagine the following:

My current closure use case is this (template.points and resampledPoints are of type [CGPoint]):

let localHighestSimilarity = zip(template.points, resampledPoints).reduce(0.0) { accumulator, points in
  let (template, resampled) = points
  return accumulator + Double(template.x * resampled.x + template.y * resampled.y)
}

To reuse this code elsewhere I maybe want to refactor the closure into a function (using your labeled tuple suggestion):

func accumulateSimilarity(accumulator: Double, for points: (point1: CGPoint, point2: CGPoint)) -> Double {
  return accumulator + Double(points.point1.x * points.point2.x + points.point1.y * points.point2.y)
}

This isn’t particularity readable (image passing a CGRect and you need the points or a more complex calculation). Compare it to this:

func accumulateSimilarity(accumulator: Double, for (point1, point2): (CGPoint, CGPoint)) -> Double {
  return accumulator + Double(point1.x * point2.x + point1.y * point2.y)
}

You can of course still pass a named tuple instead of an unnamed, but it doesn’t make any difference, which brings me to an aside*.

I think the second approach makes the calculation much more comprehensible and it just feels “intuitive” (again, at least for me) :).

- Dennis

* I’m not sure how scientifically correct the following statement is but strictly speaking (at least for me) (valueA: String, valueB: String) is not of the same type as (String, String) just like func d(string: String, int: Int) is different from func e(_: String, _: Int) though in Swift the tuples are interchangeable (you can pass one where the other is expected).

On May 8, 2016, at 6:10 PM, Geordie J <geojay@gmail.com <mailto:geojay@gmail.com>> wrote:

Comments below

Am 05.05.2016 um 20:22 schrieb Dennis Weissmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

Following a short discussion with positive feedback on [swift-users](http://thread.gmane.org/gmane.comp.lang.swift.user/1812) I’d like to discuss the following:

Tuples should be destructible into their components in parameter lists.

Consider the following code:

let a = [0,1,2,3,4,5,6,7,8,9]
let b = [0,1,2,3,4,5,6,7,8,9]

let c = zip(a,b).reduce(0) { acc, tuple in
  acc + tuple.0 + tuple.1
}

tuple is of type (Int, Int).

The problem is that the calculation is not very comprehensible due to .0 and .1. That’s when destructuring tuples directly in the parameter list comes into play:

let c = zip(a,b).reduce(0) { acc, (valueA, valueB) in
  acc + valueA + valueB
}

+1 I think this is a great way to go about it.

The above is what I propose should be accepted by the compiler (but currently isn’t).

Currently tuple destructuring is possible like this:

let c = zip(a,b).reduce(0) { (acc, tuple) in
  let (valueA, valueB) = tuple
  return acc + valueA + valueB
}

This is not about saving one line ;-). I just find it much more intuitive to destructure the tuple in the parameter list itself.

Agreed

The same thing could be done for functions:

func takesATuple(someInt: Int, tuple: (String, String))

Here we also need to destructure the tuple inside the function, but the intuitive place (at least for me) to do this would be the parameter list.

In the following example I'm making use of Swift’s feature to name parameters different from their labels (for internal use inside the function, this is not visible to consumers of the API):

func takesATuple(someInt: Int, tuple (valueA, valueB): (String, String))

I’m not such a fan of this though. I realize what I’m about to write here is discussing a slightly different point but bear with me: I was under the impression it was already possible to do something like this (maybe only possible with typealiases):

func takesATuple(someInt: Int, tuple: (valueA: String, valueB: String)) {}

I find that syntax readable and extensible: you can make a type alias for your tuple type '(valueA: String, valueB: String)‘, you can then use it like this:

func takesATuple(someInt: Int, tuple: MyAliasedTupleType) {
  print(tuple.valueA)
}

It’s true that you still have the ‚overhead‘ of having to type tuple. before accessing its members. But this is almost always what I want (hopefully you’d never actually name your tuple ‚tuple‘, instead it’d be a logical namespace for what it contains). Do you have a real-world example where you’d need this? To me it seems that in a case like this the API that produced the tuple would need refining rather than the language itself.

Here valueA and valueB would be directly usable within the function. The tuple as a whole would not be available anymore.

Now it’s your turn!

1. What do you think?
2. Is this worth being discussed now (i.e. is it implementable in the Swift 3 timeframe) or should I delay it?

Cheers,

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


(Brent Royal-Gordon) #7

// Allowed today:
func takesATuple(tuple: (Int, Int)) {
  let valueA = tuple.0
  let valueB = tuple.1
  // ...
}

// Proposed syntax:
func takesATuple(tuple (valueA, valueB): (Int, Int)) {
  // use valueA
  // use valueB
}

Personally, I find this example confusing because the label is "tuple", which kind of reads like a keyword, and because you're using the same name for the label and variable. If I understand the semantics you're proposing correctly, I think it would be clearer to write this example like:

// Allowed today:
func takes(a tuple: (Int, Int)) {
  let valueA = tuple.0
  let valueB = tuple.1
  // ...
}

// Proposed syntax:
func takes(a (valueA, valueB): (Int, Int)) {
  // use valueA
  // use valueB
}

Incidentally, it may also be a good idea to define what happens if you write:

func takes((valueA, valueB): (Int, Int))

Normally, if there's no separate label and variable name, they're the same, but you can't have a label like `(valueA, valueB)`. I see two reasonably sensible answers here:

1. It's equivalent to writing `_ (valueA, valueB)`.
2. It's illegal. You have to write a label, or `_` if you don't want one.

My preference would be for #2, but you're the designer, not me.

···

--
Brent Royal-Gordon
Architechies


(Vladimir) #8

I believe you should add currently available syntax to proposal text:

let c = zip(a,b).reduce(0) { (acc, tuple: (a: Int, b: Int)) in
   acc + tuple.a + tuple.b
}

func takesATuple(tuple : (valueA: Int, valueB: Int)) {
   print("a: \(tuple.valueA) b:\(tuple.valueB)")
}

Not so nice as proposed, but not so ugly as just tuple.0.
I'm not sure if the proposed feature is adding important improvement to the language.

···

On 30.05.2016 0:20, Dennis Weissmann via swift-evolution wrote:

Thanks for everyone participating in this discussion! :slight_smile:
I’ve drafted a formal proposal, it is available
here: https://github.com/dennisweissmann/swift-evolution/blob/tuple-destructuring/proposals/0000-tuple-destructuring.md

Please let me know what you think (it would be great if a native speaker
could take a look at grammar and spelling mistakes). Thanks!

  Tuple Destructuring in Parameter Lists

  * Proposal: SE-NNNN
    <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
  * Author(s): Dennis Weissmann <https://github.com/dennisweissmann>
  * Status: *Awaiting review*
  * Review manager: TBD

    Introduction

Tuple destructuring is the process of extracting elements from tuples.

This is valid today:

Swift

>let point = (x: 20.0, y: 31.0, z: 42.0) // Approach 1: let x = point.x let
y = point.y let z = point.z // Approach 2: let (x, y, z) = point // For-in
loops support tuple destructuring for (x, y, z) in [point] { // use x, y, z }|

Swift-evolution thread: [Pitch] Tuple Destructuring in Parameter Lists
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/16190>

    Motivation

This proposal seeks to generalize this behavior for every use case where
tuples need to be destructured. These are parameter lists in closures and
parameter lists in functions. Consistency is a major goal of Swift but it
is currently only possible to destructure tuples in the above mentioned places.

    Proposed solution

Extending tuple destructuring to parameter lists seems natural and improves
consistency in the language.

      Closures

Parameters in closures are currently not directly destructable. They can
either be accessed via |.0|, |.1|, etc. or can be destructured by assigning
them to variables in an explicit statement.

It feels natural to do this right in the parameter list itself (just like
with for-in loops).

Swift

>let a = [0,1,2,3,4,5,6,7,8,9] let b = [0,1,2,3,4,5,6,7,8,9] // Allowed
today: let c = zip(a,b).reduce(0) { acc, tuple in acc + tuple.0 + tuple.1 }
// Also allowed today: let c = zip(a,b).reduce(0) { acc, tuple in let
(valueA, valueB) = tuple return acc + valueA + valueB } // Proposed syntax:
let c = zip(a,b).reduce(0) { acc, (valueA, valueB) in acc + valueA + valueB }|

      Functions

When it comes to functions this proposal uses Swift's feature of
differentiating between internal and external parameter names.

Swift

>// Allowed today: func takesATuple(tuple: (Int, Int)) { let valueA =
tuple.0 let valueB = tuple.1 // ... } // Proposed syntax: func
takesATuple(tuple (valueA, valueB): (Int, Int)) { // use valueA // use
valueB }|

This design has no visible effects to the call site of a function but makes
it very convenient for the function author to use the tuple's elements
inside the function body.

    Impact on existing code

This feature is strictly additive and does not effect current code.

    Alternatives considered

Leave it as is destructure in a separate assignment.

- Dennis

On May 11, 2016, at 10:12 AM, Dennis Weissmann via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Thanks for all your feedback!

This is the current statistic:
Closure syntax: All positive
Function syntax: 3 (or 4) positive, 2 negative

I’ll try to address the concern Geordie and T.J. have.

func takesATuple(someInt: Int, tuple: (valueA: String, valueB: String)) {}

It’s true that you still have the ‚overhead‘ of having to
type /tuple./ before accessing its members. But this is almost always
what I want (hopefully you’d never actually name your tuple ‚tuple‘,
instead it’d be a logical namespace for what it contains). Do you have a
real-world example where you’d need this? To me it seems that in a case
like this the API that produced the tuple would need refining rather
than the language itself.

What you suggest here is not tuple destructuring but using labeled
tuples. And while I’m totally with you that this is for many cases the
better approach, I still think we should introduce it to functions as
well, for consistency and readability reasons.
In the end inconsistency is what led to this thread because tuple
destructuring is already possible today - in for loops:

letstringTuple = [("", "”), ("", "")]
for(i, j) instringTuple {}

That made me wonder if it’s also possible for closures (because I needed
it there - and eventually someone will definitely wonder if it’s possible
for function arguments as well).

You also asked me for my use case. To be honest, I don’t have one for the
function version, but imagine the following:

My current closure use case is this
(template.points and resampledPoints are of type [CGPoint]):

letlocalHighestSimilarity = zip(template.points,
resampledPoints).reduce(0.0) { accumulator, points in
  let(template, resampled) = points
  returnaccumulator + Double(template.x* resampled.x+ template.y*
resampled.y)
}

To reuse this code elsewhere I maybe want to refactor the closure into a
function (using your labeled tuple suggestion):

funcaccumulateSimilarity(accumulator: Double, for points: (point1:
CGPoint, point2: CGPoint)) -> Double{
  returnaccumulator + Double(points.point1.x* points.point2.x+
points.point1.y* points.point2.y)
}

This isn’t particularity readable (image passing a CGRect and you need
the points or a more complex calculation). Compare it to this:

funcaccumulateSimilarity(accumulator: Double, for (point1, point2):
(CGPoint, CGPoint)) -> Double{
  returnaccumulator + Double(point1.x * point2.x + point1.y * point2.y)
}

You can of course still pass a named tuple instead of an unnamed, but it
doesn’t make any difference, which brings me to an aside*.

I think the second approach makes the calculation much more
comprehensible and it just feels “intuitive” (again, at least for me) :).

- Dennis

* I’m not sure how scientifically correct the following statement is but
strictly speaking (at least for me) (valueA: String, valueB: String) is
not of the same type as (String, String) just like func d(string: String,
int: Int) is different from func e(_: String, _: Int) though in Swift the
tuples are interchangeable (you can pass one where the other is expected).

On May 8, 2016, at 6:10 PM, Geordie J <geojay@gmail.com >>> <mailto:geojay@gmail.com>> wrote:

Comments below

Am 05.05.2016 um 20:22 schrieb Dennis Weissmann via swift-evolution
<swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

Following a short discussion with positive feedback on
[swift-users](http://thread.gmane.org/gmane.comp.lang.swift.user/1812)
I’d like to discuss the following:

Tuples should be destructible into their components in parameter lists.

Consider the following code:

let a = [0,1,2,3,4,5,6,7,8,9]
let b = [0,1,2,3,4,5,6,7,8,9]

let c = zip(a,b).reduce(0) { acc, tuple in
  acc + tuple.0 + tuple.1
}

tuple is of type (Int, Int).

The problem is that the calculation is not very comprehensible due
to .0 and .1. That’s when destructuring tuples directly in the
parameter list comes into play:

let c = zip(a,b).reduce(0) { acc, (valueA, valueB) in
  acc + valueA + valueB
}

+1 I think this is a great way to go about it.

The above is what I propose should be accepted by the compiler (but
currently isn’t).

Currently tuple destructuring is possible like this:

let c = zip(a,b).reduce(0) { (acc, tuple) in
  let (valueA, valueB) = tuple
  return acc + valueA + valueB
}

This is not about saving one line ;-). I just find it much more
intuitive to destructure the tuple in the parameter list itself.

Agreed

The same thing could be done for functions:

func takesATuple(someInt: Int, tuple: (String, String))

Here we also need to destructure the tuple inside the function, but the
intuitive place (at least for me) to do this would be the parameter list.

In the following example I'm making use of Swift’s feature to name
parameters different from their labels (for internal use inside the
function, this is not visible to consumers of the API):

func takesATuple(someInt: Int, tuple (valueA, valueB): (String, String))

I’m not such a fan of this though. I realize what I’m about to write
here is discussing a slightly different point but bear with me: I was
under the impression it was already possible to do something like this
(maybe only possible with typealiases):

func takesATuple(someInt: Int, tuple: (valueA: String, valueB: String)) {}

I find that syntax readable and extensible: you can make a type alias
for your tuple type '(valueA: String, valueB: String)‘, you can then use
it like this:

func takesATuple(someInt: Int, tuple: MyAliasedTupleType) {
  print(tuple.valueA)
}

It’s true that you still have the ‚overhead‘ of having to
type /tuple./ before accessing its members. But this is almost always
what I want (hopefully you’d never actually name your tuple ‚tuple‘,
instead it’d be a logical namespace for what it contains). Do you have a
real-world example where you’d need this? To me it seems that in a case
like this the API that produced the tuple would need refining rather
than the language itself.

Here valueA and valueB would be directly usable within the function.
The tuple as a whole would not be available anymore.

Now it’s your turn!

1. What do you think?
2. Is this worth being discussed now (i.e. is it implementable in the
Swift 3 timeframe) or should I delay it?

Cheers,

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

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


(Matthew Johnson) #9

// Allowed today:
func takesATuple(tuple: (Int, Int)) {
let valueA = tuple.0
let valueB = tuple.1
// ...
}

// Proposed syntax:
func takesATuple(tuple (valueA, valueB): (Int, Int)) {
// use valueA
// use valueB
}

Personally, I find this example confusing because the label is "tuple", which kind of reads like a keyword, and because you're using the same name for the label and variable. If I understand the semantics you're proposing correctly, I think it would be clearer to write this example like:

// Allowed today:
func takes(a tuple: (Int, Int)) {
let valueA = tuple.0
let valueB = tuple.1
// ...
}

// Proposed syntax:
func takes(a (valueA, valueB): (Int, Int)) {
// use valueA
// use valueB
}

Incidentally, it may also be a good idea to define what happens if you write:

func takes((valueA, valueB): (Int, Int))

Normally, if there's no separate label and variable name, they're the same, but you can't have a label like `(valueA, valueB)`. I see two reasonably sensible answers here:

1. It's equivalent to writing `_ (valueA, valueB)`.
2. It's illegal. You have to write a label, or `_` if you don't want one.

My preference would be for #2, but you're the designer, not me.

I agree. #2 is more consistent with Swift 3 where all arguments have external names by default. I don't think this should change just because there is no direct internal name that can also serve as an external name.

I like the idea of allowing destructuring everywhere we bind a name very much. My only (minor) concern with doing this for tuples right now is that it might encourage overuse of them where a struct would be a better choice.

I have been thinking about destructuring of structs and classes and wonder if it might be best to introduce that first. That would avoid any temptation to abuse tuples just because they can be destructured. This is probably an overblown concern but it is something to consider.

Another option would be to just introduce a related proposal to destructure structs and classes at roughly the same time as the parameter destructuring proposal...

···

Sent from my iPad
On May 30, 2016, at 8:01 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

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


(Chris Lattner) #10

FWIW, Swift 1 supported tuple destructuring in parameter lists, and we took it out to simplify the language and eliminate special cases. Whereas as a very early version of swift modeled parameter lists using patterns (something very common in functional programming languages) we have progressively and intentionally move away from that approach.

-Chris

···

On May 30, 2016, at 6:01 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

// Proposed syntax:
func takes(a (valueA, valueB): (Int, Int)) {
// use valueA
// use valueB
}


(Dennis Weissmann) #11

Hi Brent,

Thanks! Those are great points!

I haven’t thought about the possibility of suppressing the external label. I like your option 2 very much! I’ll add it to the proposal and change the used variable names.

- Dennis

···

On May 30, 2016, at 3:01 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

// Allowed today:
func takesATuple(tuple: (Int, Int)) {
let valueA = tuple.0
let valueB = tuple.1
// ...
}

// Proposed syntax:
func takesATuple(tuple (valueA, valueB): (Int, Int)) {
// use valueA
// use valueB
}

Personally, I find this example confusing because the label is "tuple", which kind of reads like a keyword, and because you're using the same name for the label and variable. If I understand the semantics you're proposing correctly, I think it would be clearer to write this example like:

// Allowed today:
func takes(a tuple: (Int, Int)) {
let valueA = tuple.0
let valueB = tuple.1
// ...
}

// Proposed syntax:
func takes(a (valueA, valueB): (Int, Int)) {
// use valueA
// use valueB
}

Incidentally, it may also be a good idea to define what happens if you write:

func takes((valueA, valueB): (Int, Int))

Normally, if there's no separate label and variable name, they're the same, but you can't have a label like `(valueA, valueB)`. I see two reasonably sensible answers here:

1. It's equivalent to writing `_ (valueA, valueB)`.
2. It's illegal. You have to write a label, or `_` if you don't want one.

My preference would be for #2, but you're the designer, not me.

--
Brent Royal-Gordon
Architechies


(Dennis Weissmann) #12

Great catch! I didn’t know that was possible! I’ll add it to the proposal. Thanks!

- Dennis

···

On May 30, 2016, at 3:53 PM, Vladimir.S <svabox@gmail.com> wrote:

I believe you should add currently available syntax to proposal text:

let c = zip(a,b).reduce(0) { (acc, tuple: (a: Int, b: Int)) in
acc + tuple.a + tuple.b
}

func takesATuple(tuple : (valueA: Int, valueB: Int)) {
print("a: \(tuple.valueA) b:\(tuple.valueB)")
}

Not so nice as proposed, but not so ugly as just tuple.0.
I'm not sure if the proposed feature is adding important improvement to the language.

On 30.05.2016 0:20, Dennis Weissmann via swift-evolution wrote:

Thanks for everyone participating in this discussion! :slight_smile:
I’ve drafted a formal proposal, it is available
here: https://github.com/dennisweissmann/swift-evolution/blob/tuple-destructuring/proposals/0000-tuple-destructuring.md

Please let me know what you think (it would be great if a native speaker
could take a look at grammar and spelling mistakes). Thanks!

Tuple Destructuring in Parameter Lists

* Proposal: SE-NNNN
   <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
* Author(s): Dennis Weissmann <https://github.com/dennisweissmann>
* Status: *Awaiting review*
* Review manager: TBD

   Introduction

Tuple destructuring is the process of extracting elements from tuples.

This is valid today:

Swift

>let point = (x: 20.0, y: 31.0, z: 42.0) // Approach 1: let x = point.x let
y = point.y let z = point.z // Approach 2: let (x, y, z) = point // For-in
loops support tuple destructuring for (x, y, z) in [point] { // use x, y, z }|

Swift-evolution thread: [Pitch] Tuple Destructuring in Parameter Lists
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/16190>

   Motivation

This proposal seeks to generalize this behavior for every use case where
tuples need to be destructured. These are parameter lists in closures and
parameter lists in functions. Consistency is a major goal of Swift but it
is currently only possible to destructure tuples in the above mentioned places.

   Proposed solution

Extending tuple destructuring to parameter lists seems natural and improves
consistency in the language.

     Closures

Parameters in closures are currently not directly destructable. They can
either be accessed via |.0|, |.1|, etc. or can be destructured by assigning
them to variables in an explicit statement.

It feels natural to do this right in the parameter list itself (just like
with for-in loops).

Swift

>let a = [0,1,2,3,4,5,6,7,8,9] let b = [0,1,2,3,4,5,6,7,8,9] // Allowed
today: let c = zip(a,b).reduce(0) { acc, tuple in acc + tuple.0 + tuple.1 }
// Also allowed today: let c = zip(a,b).reduce(0) { acc, tuple in let
(valueA, valueB) = tuple return acc + valueA + valueB } // Proposed syntax:
let c = zip(a,b).reduce(0) { acc, (valueA, valueB) in acc + valueA + valueB }|

     Functions

When it comes to functions this proposal uses Swift's feature of
differentiating between internal and external parameter names.

Swift

>// Allowed today: func takesATuple(tuple: (Int, Int)) { let valueA =
tuple.0 let valueB = tuple.1 // ... } // Proposed syntax: func
takesATuple(tuple (valueA, valueB): (Int, Int)) { // use valueA // use
valueB }|

This design has no visible effects to the call site of a function but makes
it very convenient for the function author to use the tuple's elements
inside the function body.

   Impact on existing code

This feature is strictly additive and does not effect current code.

   Alternatives considered

Leave it as is destructure in a separate assignment.

- Dennis

On May 11, 2016, at 10:12 AM, Dennis Weissmann via swift-evolution >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Thanks for all your feedback!

This is the current statistic:
Closure syntax: All positive
Function syntax: 3 (or 4) positive, 2 negative

I’ll try to address the concern Geordie and T.J. have.

func takesATuple(someInt: Int, tuple: (valueA: String, valueB: String)) {}

It’s true that you still have the ‚overhead‘ of having to
type /tuple./ before accessing its members. But this is almost always
what I want (hopefully you’d never actually name your tuple ‚tuple‘,
instead it’d be a logical namespace for what it contains). Do you have a
real-world example where you’d need this? To me it seems that in a case
like this the API that produced the tuple would need refining rather
than the language itself.

What you suggest here is not tuple destructuring but using labeled
tuples. And while I’m totally with you that this is for many cases the
better approach, I still think we should introduce it to functions as
well, for consistency and readability reasons.
In the end inconsistency is what led to this thread because tuple
destructuring is already possible today - in for loops:

letstringTuple = [("", "”), ("", "")]
for(i, j) instringTuple {}

That made me wonder if it’s also possible for closures (because I needed
it there - and eventually someone will definitely wonder if it’s possible
for function arguments as well).

You also asked me for my use case. To be honest, I don’t have one for the
function version, but imagine the following:

My current closure use case is this
(template.points and resampledPoints are of type [CGPoint]):

letlocalHighestSimilarity = zip(template.points,
resampledPoints).reduce(0.0) { accumulator, points in
let(template, resampled) = points
returnaccumulator + Double(template.x* resampled.x+ template.y*
resampled.y)
}

To reuse this code elsewhere I maybe want to refactor the closure into a
function (using your labeled tuple suggestion):

funcaccumulateSimilarity(accumulator: Double, for points: (point1:
CGPoint, point2: CGPoint)) -> Double{
returnaccumulator + Double(points.point1.x* points.point2.x+
points.point1.y* points.point2.y)
}

This isn’t particularity readable (image passing a CGRect and you need
the points or a more complex calculation). Compare it to this:

funcaccumulateSimilarity(accumulator: Double, for (point1, point2):
(CGPoint, CGPoint)) -> Double{
returnaccumulator + Double(point1.x * point2.x + point1.y * point2.y)
}

You can of course still pass a named tuple instead of an unnamed, but it
doesn’t make any difference, which brings me to an aside*.

I think the second approach makes the calculation much more
comprehensible and it just feels “intuitive” (again, at least for me) :).

- Dennis

* I’m not sure how scientifically correct the following statement is but
strictly speaking (at least for me) (valueA: String, valueB: String) is
not of the same type as (String, String) just like func d(string: String,
int: Int) is different from func e(_: String, _: Int) though in Swift the
tuples are interchangeable (you can pass one where the other is expected).

On May 8, 2016, at 6:10 PM, Geordie J <geojay@gmail.com >>>> <mailto:geojay@gmail.com>> wrote:

Comments below

Am 05.05.2016 um 20:22 schrieb Dennis Weissmann via swift-evolution
<swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

Following a short discussion with positive feedback on
[swift-users](http://thread.gmane.org/gmane.comp.lang.swift.user/1812)
I’d like to discuss the following:

Tuples should be destructible into their components in parameter lists.

Consider the following code:

let a = [0,1,2,3,4,5,6,7,8,9]
let b = [0,1,2,3,4,5,6,7,8,9]

let c = zip(a,b).reduce(0) { acc, tuple in
acc + tuple.0 + tuple.1
}

tuple is of type (Int, Int).

The problem is that the calculation is not very comprehensible due
to .0 and .1. That’s when destructuring tuples directly in the
parameter list comes into play:

let c = zip(a,b).reduce(0) { acc, (valueA, valueB) in
acc + valueA + valueB
}

+1 I think this is a great way to go about it.

The above is what I propose should be accepted by the compiler (but
currently isn’t).

Currently tuple destructuring is possible like this:

let c = zip(a,b).reduce(0) { (acc, tuple) in
let (valueA, valueB) = tuple
return acc + valueA + valueB
}

This is not about saving one line ;-). I just find it much more
intuitive to destructure the tuple in the parameter list itself.

Agreed

The same thing could be done for functions:

func takesATuple(someInt: Int, tuple: (String, String))

Here we also need to destructure the tuple inside the function, but the
intuitive place (at least for me) to do this would be the parameter list.

In the following example I'm making use of Swift’s feature to name
parameters different from their labels (for internal use inside the
function, this is not visible to consumers of the API):

func takesATuple(someInt: Int, tuple (valueA, valueB): (String, String))

I’m not such a fan of this though. I realize what I’m about to write
here is discussing a slightly different point but bear with me: I was
under the impression it was already possible to do something like this
(maybe only possible with typealiases):

func takesATuple(someInt: Int, tuple: (valueA: String, valueB: String)) {}

I find that syntax readable and extensible: you can make a type alias
for your tuple type '(valueA: String, valueB: String)‘, you can then use
it like this:

func takesATuple(someInt: Int, tuple: MyAliasedTupleType) {
print(tuple.valueA)
}

It’s true that you still have the ‚overhead‘ of having to
type /tuple./ before accessing its members. But this is almost always
what I want (hopefully you’d never actually name your tuple ‚tuple‘,
instead it’d be a logical namespace for what it contains). Do you have a
real-world example where you’d need this? To me it seems that in a case
like this the API that produced the tuple would need refining rather
than the language itself.

Here valueA and valueB would be directly usable within the function.
The tuple as a whole would not be available anymore.

Now it’s your turn!

1. What do you think?
2. Is this worth being discussed now (i.e. is it implementable in the
Swift 3 timeframe) or should I delay it?

Cheers,

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

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


(Erica Sadun) #13

You can't splat but you can decompose a tuple by assignment:

let a = (1, 2, 3)
func foo(v1: Int, v2: Int, v3: Int) { print (v1, v2, v3) }

// Still works:
let (b, c, d) = a; print(b, c, d)

// And this works after the assignment:
foo(v1: b, v2: c, v3: d)

// No longer works:
foo(a) // tuple splat is gone

// These all work though:
func bar(arg: (Int, Int, Int)) { print(arg.0, arg.1, arg.2) }
bar(arg: a)
bar(arg: (b, c, d))

// You can add field names in the func's decl type
func blort(arg: (x: Int, y: Int, z: Int)) { print(arg.x, arg.y, arg.z) }
blort(arg: a) // works
blort(arg: (b, c, d)) // works

// But the following doesn't work, Error is "cannot
// convert value of (l: Int, m: Int, n: Int)"
blort(arg: (l: b, m: c, n: d))

I vaguely remember a discussion onlist about creating typealiases and then using casting to convert between structurally identical tuples but I don't think it went anywhere or had a strong use case.

-- E

···

On May 30, 2016, at 2:39 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On May 30, 2016, at 6:01 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

// Proposed syntax:
func takes(a (valueA, valueB): (Int, Int)) {
// use valueA
// use valueB
}

FWIW, Swift 1 supported tuple destructuring in parameter lists, and we took it out to simplify the language and eliminate special cases. Whereas as a very early version of swift modeled parameter lists using patterns (something very common in functional programming languages) we have progressively and intentionally move away from that approach.

-Chris


(Dennis Weissmann) #14

I like the idea of allowing destructuring everywhere we bind a name very much. My only (minor) concern with doing this for tuples right now is that it might encourage overuse of them where a struct would be a better choice.

I have been thinking about destructuring of structs and classes and wonder if it might be best to introduce that first. That would avoid any temptation to abuse tuples just because they can be destructured. This is probably an overblown concern but it is something to consider.

That’s interesting! I haven’t had the time to look at destructuring of structs and classes but a quick look at how it’s done in Rust and looks promising.
It’s a fair point you made, I don’t have a problem delaying this proposal.

- Dennis

···

On May 30, 2016, at 4:14 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On May 30, 2016, at 8:01 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

// Allowed today:
func takesATuple(tuple: (Int, Int)) {
let valueA = tuple.0
let valueB = tuple.1
// ...
}

// Proposed syntax:
func takesATuple(tuple (valueA, valueB): (Int, Int)) {
// use valueA
// use valueB
}

Personally, I find this example confusing because the label is "tuple", which kind of reads like a keyword, and because you're using the same name for the label and variable. If I understand the semantics you're proposing correctly, I think it would be clearer to write this example like:

// Allowed today:
func takes(a tuple: (Int, Int)) {
let valueA = tuple.0
let valueB = tuple.1
// ...
}

// Proposed syntax:
func takes(a (valueA, valueB): (Int, Int)) {
// use valueA
// use valueB
}

Incidentally, it may also be a good idea to define what happens if you write:

func takes((valueA, valueB): (Int, Int))

Normally, if there's no separate label and variable name, they're the same, but you can't have a label like `(valueA, valueB)`. I see two reasonably sensible answers here:

1. It's equivalent to writing `_ (valueA, valueB)`.
2. It's illegal. You have to write a label, or `_` if you don't want one.

My preference would be for #2, but you're the designer, not me.

I agree. #2 is more consistent with Swift 3 where all arguments have external names by default. I don't think this should change just because there is no direct internal name that can also serve as an external name.

I like the idea of allowing destructuring everywhere we bind a name very much. My only (minor) concern with doing this for tuples right now is that it might encourage overuse of them where a struct would be a better choice.

I have been thinking about destructuring of structs and classes and wonder if it might be best to introduce that first. That would avoid any temptation to abuse tuples just because they can be destructured. This is probably an overblown concern but it is something to consider.

Another option would be to just introduce a related proposal to destructure structs and classes at roughly the same time as the parameter destructuring proposal...

--
Brent Royal-Gordon
Architechies

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


(Callionica (Swift)) #15

This is an interesting proposal, but I think splatting like Python would be
preferable: creators of functions use separate parameters and callers can
expand tuples as necessary by prefixing a tuple at point of use with * to
expand it into separate arguments.

Even with the Swift language as it is today, it looks like the right thing
to do when you want named pieces of data is to define separate parameters
(or fall back to using a tuple parameter with named components if that's
really impossible).

Erica points out that you can do a lot with the current language. I did
some experiments to see what's possible and it's really easy to enhance
Swift to make it possible to pass tuples to functions expecting separate
parameters. There are a couple of interesting ops that can go into a
library to make things easier:
1. Anonymize a tuple
2. Wrap a function that takes separate arguments with one that takes a tuple

The downside to doing it in a library instead of the compiler is that you
have to splat the function instead of the tuple.

If you have a function 'fn' that takes separate arguments and a tuple 't'
that matches the arguments, you could call it like
(*fn)(t)
instead of
fn(*t)

That's not so bad if one tuple contains all the arguments

You can see experiments at
https://gist.github.com/callionica/43f79dd0a9b145746d72e8a8a62c2820
and I will probably write something about this at
http://www.callionica.com/developer/#swift-splat
at some point

-- Callionica

···

On Mon, May 30, 2016 at 2:09 PM, Erica Sadun via swift-evolution < swift-evolution@swift.org> wrote:

On May 30, 2016, at 2:39 PM, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

On May 30, 2016, at 6:01 AM, Brent Royal-Gordon via swift-evolution < > swift-evolution@swift.org> wrote:

// Proposed syntax:
func takes(a (valueA, valueB): (Int, Int)) {
// use valueA
// use valueB
}

FWIW, Swift 1 supported tuple destructuring in parameter lists, and we
took it out to simplify the language and eliminate special cases. Whereas
as a very early version of swift modeled parameter lists using patterns
(something very common in functional programming languages) we have
progressively and intentionally move away from that approach.

-Chris

You can't splat but you *can* decompose a tuple by assignment:

let a = (1, 2, 3)
func foo(v1: Int, v2: Int, v3: Int) { print (v1, v2, v3) }

// Still works:
let (b, c, d) = a; print(b, c, d)

// And this works after the assignment:
foo(v1: b, v2: c, v3: d)

// No longer works:
foo(a) // tuple splat is gone

// These all work though:
func bar(arg: (Int, Int, Int)) { print(arg.0, arg.1, arg.2) }
bar(arg: a)
bar(arg: (b, c, d))

// You can add field names in the func's decl type
func blort(arg: (x: Int, y: Int, z: Int)) { print(arg.x, arg.y, arg.z) }
blort(arg: a) // works
blort(arg: (b, c, d)) // works

// But the following doesn't work, Error is "cannot
// convert value of (l: Int, m: Int, n: Int)"
blort(arg: (l: b, m: c, n: d))

I vaguely remember a discussion onlist about creating typealiases and then
using casting to convert between structurally identical tuples but I don't
think it went anywhere or had a strong use case.

-- E

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


(Chris Lattner) #16

Right. That is because assignment permits destructuring. Parameter lists do not (anymore).

-Chris

···

On May 30, 2016, at 3:09 PM, Erica Sadun <erica@ericasadun.com> wrote:
You can't splat but you can decompose a tuple by assignment: