Supporting Extensions for Structural Types

The '...' signifies an arbitrary number of slots of whatever was in the slot before it (in this case T).

If we wanted, you could potentially have other types in the tuple around it, I suppose:

(Int, T, ...) //1 int slot followed by an arbitrary number of T

Ok. Should I infer that you're also suggesting we keep ...T (or a variation of it) for an arbitrary number of arbitrary types?

to try and get this conversation away from syntax and back to semantics, i just wanna point to what my vector manifesto writeup has to say about this issue, and that’s that if you want to have “an arbitrary number of T” (as opposed to “an arbitrary number of arbitrary types T), you’re probably going to want to parameterize that quantity in the form of a generic multiplicity parameter/associated concept. This is the whole idea around having GMPs.

protocol HomogenousTuple
{
    associatedtype Element 
    associatedmultiplicity N

    init(headerAndElements:(Int, Element * N))
}

struct S:Homogenous3Tuple<Element>:HomogenousTuple
{
    init(headerAndElements:(Int, Element, Element, Element)) 
    {
        ...
    }
}

Personally I'd consider this technical debt added to the compiler and language. Why wouldn't we tackle doing it right?

I realize that this could be seen as "holding hostage" a useful ergonics improvement, but you could also argue it the other way: variadic generics and non-nominal type conformances are super important language feature for lots of reasons. Doing them right is the most important thing we as a language community need to resolve. Variadic generics / non-nominal conformances are bricks in the language, hashability for tuples is spackle.

-Chris

7 Likes

I totally agree that having the compiler provide special-case support making tuples equatable and hashable, would amount to technical debt.

However, allowing for extensions of fixed-arity tuples lies somewhere in the middle between that and variadic generics. I am not familiar with the difficulty of implementing either solution in the Swift compiler so I cannot comment in the cost-value tradeoff.

One relevant question I have though is the following:

How are tuples currently represented? Because, if we can represent tuples as nested structures (e.g., something like Tuple1<T> = Tuple<T, Nil>, Tuple2<T, U> = Tuple<T, Tuple<U, Nil>, ... , TupleN<H, ...> = Tuple<H, T: Tuple>), then couldn't we support extensions for arbitrary arity tuples without using variadic generics? We would just need a base case extension and an induction-rule extension and we could derive the extension for any arity via induction. How hard would it be to support something like that? That's also how tuples types are represented in Scala 3 (as opposed to the previous manual up-to-22 element tuple types).

Preface: This is all just MHO, not representing anyone else.

I would consider that technical debt as well. Rationale: we as a community want to get the "bricks" of the larger features. When those bricks come in, special cases like this will cease to be valuable and shouldn't exist, and therefore are just debt if they don't happen to be perfectly subsumable with the new features in an ABI and source compatible way (which seems unlikely).

If we tackled general non-nominal type conformance and decided that we couldn't pull it off, then yeah of course I'd support finding second or third priority paths. I just don't want to start there.

-Chris

9 Likes

+1. A while back I tried dipping my toe into refactoring the protocol conformance machinery to support conformance for tuples (specifically Equatable and Hashable), and the current implementation is so tightly coupled to nominal types that the changes would have been pretty broadly reaching.

Now, someone who's an expert on the code base (i.e., not me) could have done that work much faster and more successfully, but given the affected area, it would definitely be better to take it all the way to its logical conclusion and not replace one set of special cases with another.

Doing it the right way might delay one particular feature that many people seem to want, but in the long run we're better off than if we do it the expedient way and it ends up causing more pain down the line that could delay other things that we also want/need.

3 Likes

That is a valid point.

For those of us who are not familiar with the compiler internals, where is a good place to start looking/reading/learning to understand what is required to add support for non-nominal type conformance?

Also, one of my confusions about variadic generics is that I don't see why they're coupled with other powerful features, such as non-nominal type conformance. I come from a Scala background where you have a very strong type system and can derive type trait instances for e.g. arbitrary arity tuple types, without the need for variadic generics. That's why I tend to think of variadic generics and non-nominal type conformance as two separate features that shouldn't necessarily depend on each other. However, given that I am not familiar with the Swift compiler, I don't know if there is something else "coupling the two".

4 Likes

Afaics, it isn't clear what kinds of tuples should be extendable (with a single declaration):
Imho it would be enough if you could extend any specific tuple (extension (String, Int), extension (name: String, height: Float): Comparable…)
Others might want to be able to extend arbitrary tuples with a single declaration, which is much more complicated and most likely needs variadic generics. I personally think neither T… nor …T is a good syntax for this feature, and would rather not have it.

I've often wished that it was possible to extend tuples, especially to conform to Hashable etc. But in all those cases I would have been equally well served by something like ExpressibleByTupleLiteral, like let g: MySpecialKeyType = (23, "wqrw").

It seems to me that extending and conforming tuples goes against the whole idea of tuples. Creating a new type is trivial and I suppose the overhead is similar.

What I would like to see is more dynamic ways to work with tuple elements though, like accessing the n:th element, or iterating over all elements. Though this might be better served by (complier enforced) fixed size arrays, since it probably makes most sense for homogenous tuples.