(I wrote fixed size arrays, and edited to add type level count, in order to make the analogy more relevant, but let's go with regular arrays.)
Sorry for repeating this but it's so easy to forget: Parentheses are unfortunately used for several entirely different purposes, two of which are:
1. To group parts of an algebraic expression, as in (x + 2) * y
. Here, they are used to induce order of operations by overriding normal operator precedence. It's easy to see how and why parentheses when used in this sense can be superfluous: The order of one or zero things (operations) always stays the same, so the following parens have no effect: (1 + (2))
. Parentheses (when used in this sense) can also be superfluous in that the precedence of the (2 or more) operators are such that the order isn't changed by the parentheses, as in eg: 1 + (2 * 3)
. The convention is to allow such superfluous parentheses. They don't change anything, and can be used to increase clarity (for human readers).
2. To identify tuple elements, as in
let tuple: (UInt8, (String, Float)) = (1, ("two", .pi))
. Here parentheses are used to identify the elements of a tuple type or literal, just like square brackets are used to identify the elements of an array. But tuple types can have a different type for each element, and the elements can have labels, and the number of elements are at the type level.
Oh, and tuples can have 0 or 2 or more elements, not 0 or more like arrays.
– But why can't they have one element?
– Well, ...
... because ...
... in Swift's early days we thought it would be possible to use tuples (including single element ones) for almost everything, but then we realized that all aspects of parameter lists (inout, @noescape etc) cannot be represented as a tuple, and since we use parentheses for a lot of things, we sometimes conflate these things, and it gets confusing, and it's kind of hard to communicate with all these different things dressed up as parentheses, but at least we solved the parameter list / function type issue by always requiring explicit parameter-list-parens but ... long story short: We've had and still have a lot of confusing discussions, bugs and inconsistencies related to tuples, argument lists, pattern matching, almost anything having to do with parentheses, and banning single element tuples seemed to solve at least some of the problems, and it's also for convenience, at least in some cases, even though it's not so great for consistency, and it does imply some other weird inconsistencies and needless limitations (for eg variadic generics). Also ((1) + (((2) * 3)))
is the same as 1 + 2 * 3
so why shouldn't ((Int))
be the same as Int
? After all, A product of types is a direct product of two or more types, so we could have used an infix * instead of parens to indicate tuples, and Haskell has no 1-tuples, at least not in their parenthesized tuple syntax.
Another big difference between the semantics of tuples and eg arrays:
I've had no trouble understanding or using Swift's arrays, their behavior seems to allow me to easily form a working mental model, the rules are few and consistent, no strange exceptions, very few things to keep in my head, I've never been surprised or frustrated by them, or worried about their design, implementation and effect on other parts of the language.
But with tuples, it's been quite the opposite for as long as I can remember. My mental model of them are a tangled mess, which might be somewhat in line with their implementation in various versions and parts of the compiler. And what the language reference says about them doesn't make much sense to me and is demonstratably not true in a lot of cases.
I'm not sure I understand what you mean, could you please clarify?
Just as a thought experiment, assuming Swift's tuples were written using eg ⟪…⟫
instead of (…)
, and ⟪⟪Int⟫⟫
, ⟪Int⟫
and Int
were three different types, and only the first two tuple types. Would there still be such irritating behaviour, and if so, can you give an example?
I guess it is possible that Int
is considered a no-label 1-tuple type, and I'd like to know if this is the case, and what practical/technical reasons and consequences it has. Here's what the Swift Book has to say about it:
All tuple types contain two or more types, except for Void which is a type alias for the empty tuple type, ().
And in Xcode 10 beta and later, we have this situation:
// we can instantiate and use labeled 1-tuples:
let a = (label: 123)
print(a.label + a.label) // 246
// but not if we specify the type explicitly:
let b: (label: Int) = (label: 123) // Error: Cannot create a single-element tuple with an element label
But it's the special casing of 1-tuples (no matter if they are "banned" or otherwise special) that I think is problematic. Special casing spreads as in @Torust's example.
It seems to me like you're just accepting this loss in expressivity and uniformity that comes from special casing 1-tuples (which in turn afaics might not even have been deemed necessary if tuples hadn't been written using parentheses).
I think it would be great if we could find some way of not special casing 1-tuples, syntactically or otherwise, and which also made all tuple expressions (not just 1-tuples) clearly distinct from parenthesized expressions, just to get rid of all potential confusion.
But if Int
has to be a tuple type for some reason, then I don't know, and I'd like to know if Int
is both a basic primitive type and a compound type?
Regarding not changing current behavior, here are some examples of the current behavior:
// This program compiles with Xcode 9.4 and Xcode 10 beta:
let t = (label: 12).label + (label: 34).label
print(t) // 46
// This program compiles with Xcode 9.4 and Xcode 10 beta:
let t = (x: "first", x: "second")
let (a, x: b) = t
print(a, b) // prints: second first
// This program compiles only with Xcode 10 beta and recent snapshots:
let t = (label : 123)
// We've just defined a single element tuple, and we can use it as such:
print(t.label) // prints: 123
switch t {
case (label: 123): print("It's 123") // <-- Will match this case
case let (label: v): print(v)
}
let (label: v) = t
print(v) // prints: 123
// This program compiles with Xcode 9.4 and Xcode 10 beta:
let `true` = !(true: true).true // Drop .true to crash compiler!
print(`true` == false) // prints true