This isn't tuple splatting, this is just a consequence of the fact that single element tuples don't really exist in Swift. (T) is always the same as T.
makeTuple("π€", 0) produces a tuple of type (String, Int). makeTuple(("π€", 0)) produces a tuple of type ((String, Int)) which, following the rule above, is realy just (String, Int). makeTuple(tuple) is the exact same as the previous.
There were multiple kinds of tuple splatting, but the one that's relevant here is when you call a function with a tuple whose elements match the arguments to the function as if you had passed each element of the tuple separately.
For instance:
foo(_ a: Int, b: Int) { }
let args = (69, b: 105)
foo(args) // You can't do this any more
This was removed from the language entirely for Swift 3, but it turned out there was one extremely common use of this feature that was too painful to keep removed. Splatting parameters to closures:
func call<Args, Result>(with args: Args, function: (Args) -> Result) -> Result {
function(args)
}
func add(_ a: Int, _ b: Int) -> Int { a + b }
// this still compiles today
print(call(with: (40, 2), function: add))
This was kept since we didn't get the language feature to express what we actually wanted until parameter packs were implemented (and those are still not fully implemented).
// This is the version of call we actually wanted.
func call<each Argument, Result>(_ argument: repeat each Argument, function: (repeat each Argument) -> Result) -> Result {
function(repeat each argument)
}
// Yes, this does actually compile in 5.9
print(call(69, 105) { $0 + $1 })
If you go past four arguments it gets so slow to compile that compiler explorer kills the process, but as I said: parameter packs aren't quite done yet.
PS.
As a side note, the big reason why this kind of tuple splatting worked in the first place is because in very early versions of Swift a function's parameters were a tuple whose elements were each of its formal parameters. But that didn't actually scale very well, so they changed it.
Try using another implementation and you'll see that the repeat in your second and third example (which are identical) only have a single element to repeat (the tuple), while the first example performs the repeat for each of the two arguments:
func makeTuple<each Element>(_ element: repeat each Element) -> (repeat each Element, String) {
(repeat each element, "end")
}
makeTuple("π€", 0)
// ("π€", 0, "end")
// three elements: two from the input, plus an additional one
makeTuple(("π€", 0))
// (("π€", 0), "end")
// two elements: a single tuple input, plus an additional one
I tend to use "tuple splat" to refer specifically to the implicit conversion from (T, U, V, ...) -> W to ((T, U, V, ...)) -> W that we allow in function argument position. I think you're using it to refer to this behavior where a one-element tuple is unwrapped after substitution, which is quite different.
I think he might be talking about my comment that the variadic version of call gets unusably slow to compile after four elements. But I still am not sure what he means by that because that's clearly just an issue with the current implementation and not an inherent limit of parameter packs.
The solution to that type-checking performance issue is to get rid of bi-directional inference between single-statement closure bodies and their surrounding context. Try this: