The Generics Manifesto and single-element tuples

I'm not sure how such a model would handle the case where T... is an empty list, ie could there be an empty Tuple<> that is different from Tuple<Void>?

This problem does not exist in the current ~corresponding formulation from the generics manifesto:

extension<...Elements : Equatable> (Elements...) : Equatable {   // extending the tuple type "(Elements...)" to be Equatable
} 

(Disregarding the question about whether all the elements of an empty list of types could be considered as conforming to Equatable.)

And I guess, we'd also have to ask ourselves whether we should special case single element Tuple<T> types or not, that is: If eg the type Tuple<Int> should be allowed and/or be equivalent to Int.

IMO the obvious answer is that there should be no special casing, ie the type Tuple<Int> should be allowed and not be equivalent to Int.

It is for the same reason that I think Swift should not special case single element tuple types like it currently does. However, that would require fixing the parentheses issue (the conflation of different concepts caused by overloaded parentheses).

I think the type should be Tuple<...T> : ExpressibleByTupleLiteral instead of Tuple<T...>. The only issue I see is the empty tuple which would require some special casing. ExpressibleByTupleLiteral would always produce a tuple of at least one element. Furthermore we'd need variadic optional labels for each type.

Bikeshedding:

struct Tuple<...label?: ...T> : ExpressibleByTupleLiteral

The extension can be very similar:

extension Tuple : Equatable where ...T : Equatable { ... }

We can also drop the following special case T == (T) or T == Tuple<T> and make it T != Tuple<T>. Furthermore we can easily have Tuple<label: T> types or if we really want (label: T).

@Jens, to clear the way for these sorts of potential improvements and others, perhaps it is time to start formulating a pitch to revise the definition of the tuple type.

1 Like

My feeling is that it'd be premature to propose Tuple<T...> or ExpressibleByTupleLiteral now. Without variadic generics, neither feature would be adequately expressive in the language today, and the concerns raised in this thread are largely theoretical rather than practical without variadics.

My own experience doing variadic generic programming in C++ and the language I worked on previous to Swift, which had variadics and a distinction between Tuple<T> and T, was that it was a major pain for expressive variadic APIs, since it was almost always necessary to either special-case the unary case not to produce a tuple or do goofy ad-hoc testing to try to accommodate either a scalar or one-element tuple uniformly (which would then break when you want the tuple to be preserved). Swift's parametric generics system and tuple design gives us a great opportunity to completely obviate this concern, and I think it's premature to throw that away.

5 Likes

y’all need to stop getting so hung up on the parentheses syntax issue. this is a solved problem. just add a trailing comma before the ) to differentiate.

The original question in this thread hasn't ever been answered because, essentially, it's unanswerable. Swift had one model of the relationship among parenthesized expressions, tuples, and parameter lists, but this was abandoned as unworkable. Now it has another model, but this has not been completely implemented. Difficulties arise from the fact that there is no complete implementation of any one design. The solution isn't to write off the design as flawed and to propose another one: that's how you get three incompletely implemented designs.

6 Likes

Perhaps at least the following simple question (which I think has never been clearly answered) can be answered:

According to the model Swift intends, in the Swift language (not in its impl), does 1-tuples exist?

(I assume the answer must be “no”, because otherwise all types would be tuple types, since T == (T).)

Sorry. I wasn't clear. My intent was narrow--merely contemplating a pitch that the tuple type include a single-element tuple, as it did previously. It seems to me that that would be a first step down the path. But...

I hear your caution, and accept is as prudence. Perhaps there is little point in addressing the single-element tuple until we have a better picture of how variadic generics will be handled.

I love it, but the trailing comma is not part of the current Swift syntax (it produces an error), so perhaps the parentheses syntax issue isn't really solved, yet.

@xwu, thank you for that assessment. It seems to me that we all could benefit from understanding what that "another model" is supposed to be, what its current state actually is, and the plan for moving forward, whether that be towards finishing the implementation or moving towards a different model. It feels like a bit of a mystery. Can you point us towards documentation or enlightening discussion threads?

1 Like

meaning, there is unambiguous syntax available if we ever want to make 1-tuples a thing again

:+1:

am i right in understanding that the problem lies with tuple nesting and not with 1-element tuples themselves? and the confusion is between a 1-tuple that could be interpreted as containing an N-tuple that contains the elements, and an N-tuple directly?

((T, U, V),) vs (T, U, V) // equivalent???

I think it's not inaccurate to say that, independent of implementation, the model intended today makes no distinction as to whether unlabeled 1-tuples exist or not. There is no feature of the language that turns on that distinction, so it is not made. Labeled 1-tuples are not intended to exist, and any situation in which it is possible to create one is a bug.

I don't think it's documented anywhere specifically. The discussion threads regarding SE-0110 are enlightening, as are discussions about removing tuple shuffling and changing the syntax surrounding enum associated values.

Would it be inaccurate to say:

  • Short answer: No, of course not.
    Longer version: 1-tuples does not exist in the intended model, because if they did, then these single element tuple types would be equivalent to their element type and thus every type T would be a(n unlabeled single element) tuple type, including eg Int, Void, [Int : Float?] and (Int, Bool, String).

?


A number of possible future language features (ExpressibleByTupleLiteral, enabling extension of tuple types, etc) become unnecessarily complicated to think and talk about if we are not sure about whether 1-tuples exist or not. It should suffice with a simple "yes" or "no".

1 Like

Again, I don't think the answer is currently either yes or no.

Do you mean that this inability to say if 1-tuples exist or not is a desired property of the intended model?

Neither. Let me use an analogy. There are three answers to the question of whether someone believes in a deity: you can affirm, you can deny, or you can be agnostic. But those answers don't really apply to the question of whether someone believes in a teapot circling the universe. You can't even really be agnostic; it's simply not a question that's contemplated.

Similarly, the existence of 1-tuples is not within the set of questions that's contemplated by the language. You can argue that we ought to contemplate it now and decide on an answer; however, if your question is whether (prior to now) 1-tuples exist or do not exist, or whether the language is deliberately agnostic to the answer, that is unanswerable.

Let me make a rough summary of this thread.

My original post ends with the following question:


The Language Reference is pretty clear IMHO that 1-tuples don't exist:

All tuple types contain two or more types, except for Void which is a type alias for the empty tuple type, ().


@Tino and @taylorswift says it's weird that they don't exist:


@Joe_Groff says they do exist and are equivalent to the type of their element:


@xwu says that we cannot even be agnostic about their existence:


I think it would be nice to decide on an answer, yes : ).

Because until we do, we can't reason and communicate about matters related to tuple types (especially for example ExpressibleByTupleLiteral, enabling extension of tuple types and variadic generics), as demonstrated by this thread.

My current understanding (of the relevant part) of the intended tuple model is this:

  • – Does 0-tuples exist?
    – Yes

  • – Does 1-tuples exist?
    – There currently is no answer, but the type of a 1-tuple is probably equivalent to the type of its element.
    – Hmm, OK, so all types are probably tuple types then? That is, both String and (a: Bool, b: Float, c: String) are examples of unlabeled single element tuple types … :thinking:

  • – Does 2-tuples exist?
    – Yes, there are 2-tuples, 3-tuples, 4-tuples, and so on (and their type is separate from, but decided by the types of their elements and their order).


If it turns out that the question ("does 1-tuples exist?") truly is irrelevant, then I think that we should decide on a clear explanation of the current model.

This explanation must succeed in making the reader see that that question is obviously irrelevant, because I'm probably not the only one who currently sees that question as relevant, or even fundamental, for understanding and talking about Swift's tuples.

2 Likes

instead of all these debates about teapots and deities how about we stop and think about what this means for the things we actually want to use them for.

I wrote a function that takes three arguments.

func foo1(a:Int, b:Int, c:Int)

Can I call it like this?

foo1(1, 2, 3)            // currently doesn’t work

foo1((1, 2, 3))          // currently doesn’t work

foo1(a: 1, b: 2, c: 3)   // ✅

foo1((a: 1, b: 2, c: 3)) // currently doesn’t work

I wrote a function that takes a single tuple argument with 3 elements.

func foo2(_ arg:(a:Int, b:Int, c:Int))

Can I call it like this?

foo2(1, 2, 3)            // currently doesn’t work

foo2((1, 2, 3))          // ✅

foo2(a: 1, b: 2, c: 3)   // currently doesn’t work

foo2((a: 1, b: 2, c: 3)) // ✅

What if the argument label is required at the call site?

func foo3(arg:(a:Int, b:Int, c:Int))

Can I call it like this?

foo3(arg: (1, 2, 3))            // ✅

foo3(arg: ((1, 2, 3)))          // ✅

foo3(arg: (a: 1, b: 2, c: 3))   // ✅

foo3(arg: ((a: 1, b: 2, c: 3))) // ✅

Should this be allowed?

func foo4(arg:(a:Int)) // currently doesn’t work

Can I call it like this?

foo4(arg: 1)        // according to foo3, this should work

foo4(arg: (1))      // according to foo3, this should work

foo4(arg: (a: 1))   // according to foo3, this should work

foo4(arg: ((a: 1))) // according to foo3, this should work

What about this?

func foo5(_ arg:(a:Int)) // currently doesn’t work

Can I call it like this?

foo5(1)      // according to foo3, this should work. according to foo2, it shouldn’t

foo5((1))    // this should work, but doesn’t.

foo5(a: 1)   // this shouldn’t work.

foo5((a: 1)) // this should work, but doesn’t.

Should this be allowed?

let a:Int = (b: 35) // currently doesn’t work

What about this?

let a:(b:Int) = (b: 35) // currently doesn’t work

If I have this:

let a:Int = 13
let b     = a

what is the type of b? is it Int or (a:Int)?


If I have this:

let a = (c: 13)
let b = a

what is the type of b now? Int, (a:Int), or (c:Int)?


I want to write a variadic vector addition function that can also take a scalar, so that I can add Ints, (Int, Int)s, (Int, Int, Int)s, and so forth. Should I be able to do this?


I want to make the above function generic, so that I can add Ts, (T, T)s, (T, T, T)s, and so forth. Should I be able to do this?


I want to do the same thing as above, but allow T itself to be a 3-tuple type. If I add two T values, am I adding them as two 3-vectors, or two scalars?

6 Likes

I agree that the solution isn't to write off the intended design as flawed. But discussions such as this, and also bug reporting and fixing, would probably be more productive if the intended model/design was clearly documented, including any relationship among tuples, parenthesized expressions, parameter lists, etc. Such a document should also make it clear if/how the model makes questions like "Does 1-tuples exist?" unanswerable/irrelevant.

Can someone more experienced than me please start a rough sketch?
(I suppose it would end up here as eg Tuples.md.)

This document could then be announced in a separate thread, and discussions like this could continue (more productively) there, while establishing a common understanding by referring to and improving the document.

1 Like

@xwu, thank you for pointing me towards some of the history. Oh, man, what a story it is.

I was vaguely aware of the issue surrounding the implementation of SE-0110, but not the details or the extent of how it all started, how it ended up in trouble, and where it stands, now. If one were to look for technical debt related to the current state of tuples and parentheses, there it is. I feel bad for those guys--trying to make changes in those parts of the compiler seems fraught with hidden perils. :face_with_head_bandage:

Having recently spent a substantial amount of time tracing through parser and type-checker code to debug a tiny parentheses/tuple-related issue, I've got to say that I would love to see a summary of the current principles, rules, algorithms, etc., governing the parsing and type-checking of parentheses, tuples, function signatures, and the like. I don't suppose anyone might have a set of notes sitting in a drawer? I could start at main and walk through it from beginning to end, but where to find the time...

3 Likes