Why can you label inaccessible tuple elements in a for loop?

Is there utility in this?

for (int, bool) in [(1, true)] {
  print(bool, int) // Compiles
}

for tuple: (int: Int, bool: Bool) in [(1, true)] {
  print(tuple.bool, tuple.int) // Compiles.
}

for (int: Int, bool: Bool) in [(1, true)] {
  print(bool, int) // ❌ Cannot find 'bool' in scope. Cannot find 'bool' in scope.
}
1 Like

In the third for loop, int and bool are treated as labels for the tuple elements, while Int and Bool are local variables (of types Int and Bool respectively) that shadow Swift.Int and Swift.Bool. So the elements aren’t really inaccessible in the scope, they’re just named Int and Bool. I guess this is useful for renaming tuple elements, though I feel this syntax is more confusing than useful.

It seems like the compiler is implicitly coercing [(1, true)] to [(int: 1, bool: true)] in order for this to work, though I’m not sure why it does this. If you replace the array with a differently-labeled tuple (like [(e1: 1, e2: true)]), the code now fails to compile as the tuple has no elements labeled int or bool.

This compiles:

for i: Int in [1, 2, 3] {
   print (i)
}

So should the third one, which is just explicitly declaring the types of the tuple elements.

That’s right.

You can implicitly convert a tuple into another tuple type with the same elements but with labels added or removed. For example, (Int, String) converts to (x: Int, y: String).

There is one more implicit conversion allowing you to permute labels, for example (x: Int, y: String) converts to (y: String, x: Int).

I’m not a huge fan of tuples with labels, it doesn’t seem terribly useful and it introduces a lot of complexity in the type checker. But most of the behavior is easily explainable at least.

4 Likes

Huh! So, it's not what I thought it looked like—instead, it's a mapping. I did not know you could do that. It's sort of broken, though:

All Compile:

for (a: b, b: a) in [("a", "b")] {
  print(a, b) // "b a"
}
let (a: b, b: a) = ("a", "b")
print(a, b) // "b a"
let (a: b, b: a) = (a: "a", "b")
print(a, b) // "b a"
for (a: b, b: a) in [(a: "a", "b")] as [(_, _)] {
  print(a, b) // "b a"
}

Does not compile. Three Conflicting arguments errors:

for (a: b, b: a) in [(a: "a", "b")] { }
1 Like

Also weird:

This doesn't really look like "assignment" to me, but maybe I could get over it.

let tuple = (a: "a", b: "b", c: "c")
let (a: b, b: a, c: c) = tuple
print(a, b, c) // b a c

I think this warning is inaccurate. How is this "shuffling", but the version above is not? Mapping out of order doesn't mean "shuffle", to me…

// ⚠️ Expression shuffles the elements of this tuple; this behavior is deprecated
let (a: b, c: c, b: a) = tuple 

“Shuffle” means to apply a permutation in this case. That’s what your code is doing, changing the order of the elements in the tuple by permuting the labels.

2 Likes

Got it.

So a true flexible mapping is not actually possible; all tuple elements must be accounted for.

let tuple = (a: "a", b: "b", c: "c")
let (x, b: y, b: z) = tuple // ❌ Cannot convert value of type '(a: String, b: String, c: String)' to specified type '(String, b: String, b: String)'

…which gets weird, with the deprecated behavior:

let tuple = (a: "a", b: "b", c: "c")
let (x, y, b: z) = tuple // ⚠️ Expression shuffles the elements of this tuple; this behavior is deprecated
print(x, y, z) // a c b