The Generics Manifesto and single-element tuples

:+1:

I agree.

1. Removing exceptions has a value by itself, and can often add power to the language by simplifying its components while at the same time allowing for better compositionality.

2. Introducing exceptions motivated only by small wins in specific use cases, in acts of unexamined pseudo-pragmatism, probably always adds more complexity and confusion than power to the language.

I guess I'm worried that the issues at hand (implicit tuple splat of the existing-yet-non-existing-1-tuples, the implication that all types are tuple types, uncertainty on how and when various different cases of variadic generics should be special cased, messy implementation, etc.) might be symptoms of Swift Evolution having been doing too much of 2 and too little of 1 (on top of conflating different concepts all dressed up as parentheses).

I also guess that many will dismiss this view as naive idealism or, if I manage to conceal my lack of actual knowledge well enough, too academic.


Yes, and AFAICS both variants of the print statement would be valid code, as would this:

func foo<...T>(_ types: T.Type...) {
    print("A:", scan(types))
    print("B:", scan(T...))
}

It would be interesting to see the behavior of eg

foo()
foo(Int.self)
foo(Int.self, Bool.self)
foo(Int.self, Bool.self, String.self)

for these two different models for tuples:

  • The type of a 1-tuple is equivalent to the type of its element (the current/intended one).

vs

  • The type of a 1-tuple is separate from the type of its element, just like the type of any other N-tuple (the IMO more consistent model that I'd prefer, assuming solved conflated-parens-issue).

I'm too tired to try and figure that out though.

I'm pretty sure this discussion is grounded in the real world. In fact, this thread started with Generic Manifesto's proposed real-world use of single-element tuples. Consider:

2+2, 3+1, 10-6, and 4, each can be equated to the same value, but each models a different real-world event. They are different things.

T and (T), each can be equated to the same MetaType, but each models a different real-programming-world event. They are different things.

I don't recall any of these matters being decided by evolution proposals, so I think your blame might be misplaced here. There were proposals to try to deal with certain aspects around tuples, particularly SE-0029 and SE-0110, but moving away from modelling parameter lists as tuples, banning single-element tuples, etc were all done outside of, and generally before, the evolution process. I remember that one of the earliest Swift bugs that I reported, before it was an open source project, was that the removal of labelled single-element tuples broke the ability to label enum cases with a single piece of associated data.

2 Likes

Right, I should have written just "Swift" instead of "Swift Evolution" (but I'd still be happy to see Swift Evolution doing a bit more of 1).

Not super important, but ExpressibleByTupleLiteral and a library level way to destructure values into tuples would be "nice to have" for Python interop.

-Chris

5 Likes

To add on to that, if we remodel tuples in terms of a specially-treated Tuple<T...> type, we would have a natural syntax for expressing extensions and conditional conformances on tuples:

extension Tuple<T...>: Equatable where T: Equatable { ...

This absolves the need to define new grammar to support (T...) in the extension declaration.

That’s not actually the syntax we use for extensions on generic types. Do you mean extension Tuple: Equatable?

1 Like

Ah, I wasn't clear. I was presupposing the implementation of the syntax at the end of the section on Parameterized Extensions. Although, I didn't quite get that right, either :stuck_out_tongue:

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???