Destructuring Tuple of Tuple in Closure

Closure tuple does not support destructuring in Xcode 9 Swift 4

Let's start with the definition of flatMap for a dictionary which is the following:

func flatMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

You see that the transform closure takes only one parameter of type Element where Element is just a typealias for a tuple:

public typealias Element = (key: Key, value: Value)

So the first and only argument of the closure should be a tuple of two elements (key of type Key and value of type Value).


Now, if you look at your code (which compiles in Swift 3), you will see that this is not the case, and you should be asking why does this even work in Swift 3.

try flatMap({ (key, value) in
return try transform(key, value)
})

Your closure takes 2 arguments instead of one (key of type Key and value of type Value). This works in Swift 3 thanks to a feature called destructuring where the compiler will automatically transform a tuple of 2 elements into 2 arguments.

But this feature is weird, rarely used and gives unexpected results most of the time so it has been removed in Swift 4.
Edit: As pointed out by OOPer, this feature has been temporarily removed in Swift 4 beta but should be re-added before the final version is out.

Instead you should be writing:

try flatMap({ tupleArgument in
return try transform(tupleArgument.key, tupleArgument.value)
})

And your flatMap function becomes:

func flatMap<KeyPrime, ValuePrime>(_ transform: (Key, Value) throws -> (KeyPrime, ValuePrime)?) rethrows -> [KeyPrime:ValuePrime] {
return Dictionary<KeyPrime, ValuePrime>(elements: try flatMap({ element in
return try transform(element.key, element.value)
}))
}

Swift 5.2 tuple destructuring in closure has inconsistent behaviors

It is related to this proposal, which was once accepted in Swift 4.0.

SE-0110 Distinguish between single-tuple and multiple-argument function types

So, two of your examples (not last two though) caused error in a certain version of Swift, but Swift team found some unacceptable regression in this implementation.
Additional Commentary

And since a certain version of Swift (I do not remember the exact version, it might have been 4.0.x or 4.1), you can pass two argument closure to the parameter of function type taking a single tuple.

And recently, this exception is confirmed to be a part of SE-0110 and if some more modification is needed, a new proposal will be made and go through the usual Swift Evolution process.

Update on SE-0110 and SE-0155

the original proposal as reviewed did not include the special-case
conversion from (T, U, ...) -> V to ((T, U, ...)) -> V for function
arguments. In response to community feedback, this conversion was
added as part of the Core Team's acceptance of the proposal.

The Swift book may not have clear explanation of this behavior, but it surely is documented.

Destructuring tuples in JavaScript

Use a nested map(), get the index from the first, en use that to create each 'column':

const [ ] = tuple[0].map((_, i) => tuple.map((_, j) => tuple[j][i]))

const tuple = [[1,"word",3,4,5],[1,"hello",3,4,5],[1,"word",3,4,5]];

const [ item1, item2, item3, item4, item5 ] = tuple[0].map((_, i) => tuple.map((_, j) => tuple[j][i]));

console.log(item2[0], item2[1]); // word hello
console.log(item4); // [ 4, 4, 4 ]

Is it possible to unpack a tuple into function arguments?

On a nightly compiler:

#![feature(fn_traits)]

fn main() {
let tuple = (10, Vec::new());
std::ops::Fn::call(&foo, tuple);
}
fn foo(a: i32, b: Vec<i32>) {
}

There is AFAIK no stable way to do that.

Can I destructure a tuple without binding the result to a new variable in a let/match/for statement?

No.

Destructuring is something you can only do with patterns; the left-hand side of an assignment is not a pattern, hence you can't destructure-and-assign.

See proto-RFC 372 (Destructuring assignment) which discusses the possibility of adding this feature.

How do I destructure a tuple so that the bindings are mutable?

You've got a few problems:

  • You've put the &mut in the wrong place; &mut is part of the type, not the argument (unless you're destructuring the argument, which you aren't).

  • You can't call the argument struct, because that's a keyword.

  • You can't assign to a mutable reference with straight assignment.

So, with those in mind, here's a working solution:

#[derive(Debug)]
struct MyStruct {
tuple: (i32, i32),
}

fn function(s: &mut MyStruct) {
let (ref mut val1, ref mut val2) = s.tuple;
*val1 = 1;
*val2 = 2;
}

fn main() {
let mut s = MyStruct { tuple: (0, 0) };
function(&mut s);
println!("{:?}", s);
}

The key here is that ref in a pattern binds by-reference; combining that with mut gives you a mutable reference. Specifically, it gives you a pair of &mut i32s. Since these are references, you have to de-reference them in order to assign through them (otherwise, you'd be trying to re-assign the reference itself).



Related Topics



Leave a reply



Submit