Supporting Extensions for Structural Types

Apologies, you're right. Big brain fade there, that's exactly the purpose of variadic generics. ;)

2 Likes

And a small note regarding the operators, I think we need both even if they're ugly (we could deprecate the current and come up with new ones that improve readability).

Consider these two types where the operators have different meanings.

struct A<...T> {}
let a: A<Int, String, Bool> = ...

struct B<T...> {}
let b: B<Int, Int, Int> = ...
let invalid_b: B<Int, String> = ... // error `T` must be of the same type.

This needs a new postfix operator .... for this purpose.

Well to get back to the original topic, in case of tuple I prefer a revamp as a struct with variadic generics, in case of functions I'm not sure how useful it would be to conform those to protocols.

1 Like

That will make tuples structural types instead of nominal type thus making it similar to C# Tuple type, therefore making it extendable.

I think this conversation was derailed. I actually wanted to avoid mentioning variadic generics for now, because I believe that's a more complicated discussion. Personally, I would also prefer a revamp for tuples, as a struct with variadic generics, but for now, I am suggesting something simpler.

Why not simply allow extensions/conformances for tuples of fixed arity? I agree with the comment in this old thread that we may run into some complexity risk by encouraging people to make a bunch of conformances at different arities. However, it would still reduce the current boilerplate that is required, where when we want that behavior we simply wrap tuples of fixed arity in struct and make those structs conform to the desired protocols. So, why not go for this solution until variadic generics are supported?

I also believe the two issues are kind of orthogonal, in that support for variadic generics could still be added later and this feature would be unaffected.

IIRC, the historical reason was because of limitations in the type system implementation and the runtime metadata for those kinds of types. I can't speak to whether those limitations were locked into with ABI stability, but I hope not.

1 Like

It seems to be the same thing every time this issue comes up, but I think that there is a very annoying unsatisfactory hole in the type system right now that is not at all obvious to a beginner to the language, so that people will start using tuples in places until they realise that they've painted themselves in a corner because tuples can't even implement Equatable (and hence you simply can't compare arrays of tuples).

Instead of fixing this right away, people want to put the cart before the horse and figure out variadic generics, an issue that is probably very hard to get right (like, how many languages have actually solved this problem? how much prior art in industry and/or PL theory is there about this?) and might either never be resolved fully or at least take a long while to be addressed.

I think a more pragmatic approach as suggested by @eaplatanios makes a lot of sense.

4 Likes

While we're kind-of on the subject, it drives me nuts that metatypes don't conform to Hashable.

var someData = [AnyHashable: String]()
someData[Int.self] = "integer" // Nope.
someData[AnyHashable(Int.self)] = "integer" // Nope.

I guess the same also wouldn't work with a tuple.

Aside the problem you have mentioned which I also dislike and wish we had Type<T> type for one part of the metatypes, you can workaround your issue:

var someData = [AnyHashable: String]()
someData[ObjectIdentifier(Int.self)] = "integer"

Thanks @Fryie! I agree with you. In this case, it would be great if someone could answer the following couple questions:

  1. What is it that makes extending tuples hard right now? Why are tuples not represented as structs?
  2. What would it take to add support for this feature, without worrying about variadic generics for now? Are there some good starting points about where to look and what needs to change?

In struct R<T...>, there is no single type β€œT”, so the func bar(_: T...) must take all the generic types β€œT...”. There is no meaning to β€œa variadic argument list of just type T”, because there is no type T.

I don’t know what (if any) syntax there will be for enumerating the individual types within T..., but if we use a strawman of indexing into T then you might be able to write, eg:

func bar(_: T[0]...)

(Which would presumably crash at runtime if T... is empty.)

Requiring that multiple generic parameters be the same is…not really desirable. Your struct B only has one generic parameter, so it should not use variadic generics at all. If it needs a generic multiplicity parameter as well, then I recommend heading over to the Generics Vector Manifesto thread.

Quite interesting idea you have here. Just thinking out loud my thoughts that something similar could be used on types to express a fixed length of variadic parameters. struct Tuple<T[1...]> or struct W<T[2 ..< 6]> (a range expression to statically define the minimum and maximum bound of parameters).

You meant Vector not Generics.

1 Like

I don't know if things have changed in the he intervening year and a half, but back in Nov of 2017 @John_McCall made a couple of posts that partially answers your questions. They're both from the same thread so the forum software names them the same, but these two links really are different :slight_smile:

  1. Synthesizing Equatable, Hashable, and Comparable for tuple types
  2. Synthesizing Equatable, Hashable, and Comparable for tuple types

Assuming nothing has changed WRT this in the past year and a half, the notable quote relevant to this thread is, "I think it would be difficult to allow useful user-defined extensions/conformances of arbitrary tuple types without variadic generics". I'd love to know if he (or anyone else who's sufficiently familiar with the compiler) thinks this is still the case.

IIRC @Douglas_Gregor is (or at least was at one point) the generics guy, so maybe he has thoughts on the matter as well.

Nothing has changed (or will change) about that assessment: any mechanism that makes it possible to do non-trivial user-defined extensions of arbitrary tuples is essentially providing some sort of variadic generics.

1 Like

Do you think it would be worthwhile at some point to have the compiler and runtime provide special-case support making tuples equatable and hashable but nothing user-defined?

6 Likes

If this was feasible it would be pretty awesome to have. I have a custom Unit type for the sole reason that Void is not Equatable and Hashable and would love to ditch it.

1 Like

i suspect Hashable and Equatable form 95% of the use cases for this feature, and tuple-based vector libraries make up the other 5%

1 Like

One truly horrible workaround would be an untyped AnyTuple wrapper type that conforms to Hashable and contains a tuple, implementing equality and hashing via reflection on the underlying tuple value.

1 Like

Yes, I think it would be useful to provide special-case conformances for tuple equatability and hashability. We could certainly just generate them magically in the runtime, although I think we'd almost certainly want to also allow devirt/specialization/inlining at the SIL level.

9 Likes