Artem Stepanenko Artem Stepanenko - 1 month ago 9
Swift Question

Weird unwraping of tuples in Swift

On the picture below you can see the code executed in a playground which doesn't feel right. But the Swift compiler is completely fine with it. For some reason the embedment depth of the tuple is reduced to one.

To be more expressive:


  • The first map call causes a compilation error. (Strange!)

  • The second call is okay. (Strange!)



Does anybody know is it a bug or a feature?

Playground example

Answer

(Int, Int) is a tuple type (where the parantheses are part of its type), just as ((Int, Int)) is the same tuple type, but wrapped in an extra pair of (redundant) parantheses, just as (((Int, Int))) is the same tuple type as the two previous ones, but wrapped in two sets of (redundant) parantheses.

var a: (((Int, Int)))
print(type(of: a)) // (Int, Int)

Additional parantheses only come into effect if you start combining different types on a nested level, e.g.

var a: ((Int, Int), Int)
print(type(of: a)) // ((Int, Int), Int)`.

Now, why do the first map closure fail, whereas the 2nd do not?

When use trailing closures, you may either

  1. Use shorthand argument name ($0, ...), or
  2. Use explicitly named (or explicitly name-omitted, '_') parameters.

Both your examples attempt to use named parameters, but only the 2nd example follows the rules for using name parameters: namely, that the parameter names must be supplied (supplying parameter types and closure return type is optional, but in some cases needed due to compiler type inference limitations).

Study the following examples:

/* all good */
arr.map { (a) in
    2*a /* ^-- explicitly name parameter */
}

// or
arr.map { a in
    2*a /* ^-- explicitly name parameter */
}

/* additional parantheses: 
       error: unnamed parameters must be written with the empty name '_'

   the closure now believes we want to supply parameter name as well as 
   an explicit type annotation for this parameter */
arr.map { ((a)) in
    2*a /*  ^-- compiler believes this is now a _type_, and prior to realizing 
                that it is not, prompts us for the error that we have not 
                supplied a parameter name/explicly omitted one with '_' */
}

/* additional parantheses: 
       error: use of undeclared type 'a' */
arr.map { (_: (a)) in
    1   /* ^-- type omitted: compiler now realize that 'a' is not a type */
}

/* fixed: all good again! */
arr.map { (_: (Int)) in
    1
}

In your first example you wrap your tuple in the attempted naming of the tuple elements (in the closure's .... in part) in paranthesis (just as the errors shown above), which means Swift believes it to be a type (type (x, y)), in which case the compiler requires including an internal parameter name or explicitly omitting one (using _). Only when you supply a parameter name will the compiler realize that x and y are not valid types.

In your 2nd example you simply directly bind the closures two tuple members to the internal parameter names x and y, choosing not to explicitly type annotate these parameters (which is ok).