Supporting Extensions for Structural Types

Hi,

Currently, only nominal types can be extended; structural types like tuples and function types cannot be. Is there some fundamental limitation preventing support for structural types? If not, what would be required to add support for structural type extensions?

One example use case is that of using tuples to denote groups of elements which conform to some protocol and where the group has a statically known size. For example, in Swift for TensorFlow, it is often useful to build APIs on top of tensor groups. Such groups can be arrays of tensors, for example, but it would be great if we could also support conforming tuples of tensor groups to the TensorGroup protocol. This limitation results in undesirable structs, such as TensorPair, which have the same semantics as tuples of TensorGroups.

Thanks,
Anthony

2 Likes

If possible I would rather want non-empty tuples to become something like struct Tuple<...T>: ExpressibleByTupleLiteral with some support for generic parameter labels in the future.

5 Likes

I agree, with the proviso that “...T” is utterly abominable, and the correct spelling is clearly “T...”. :-)

5 Likes

Can you explain why it would be a postfix instead of a prefix ...?

I was using it in the sense of the generic parameter list form from here: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#variadic-generics

1 Like

Because the prefix version is ugly. :-)

It should be the same spelling for both declaration and usage:

struct Foo<T...> {
  var x: T...
}
4 Likes

My recollection is that ...T and T... were the straw man syntax suggestions for a “variadic number of generic arguments whose type(s) aren’t all the same” and “a variadic number of generic arguments of type T”, respectively.

I think. Don’t quote me on that.

Edit: By “the straw man syntax...”, I mean that I’m pretty sure somebody suggested it at some point, not that it was ever the defacto syntax for those semantics, or even that anyone liked it (other than whoever suggested it, presumably).

4 Likes

I really suggest that we use the postfix style for two reasons:

  1. The prefix style is ugly and it looks alot like C language whereas the postfix style is way more swiftier.
  2. The postfix style is already used in Swift during the declaration of variadic argument types and should not be different in declaring variadic generic parameter list.

This are my reasons for choosing the postfix style.

Any syntax for that feature is fine by me. I'm just worried about the postfix operator because it will read ambiguous in functions regardless of the ugliness of any these operators.

func foo(_: Int...) {} // variadic `Int` and only `Int`

struct R<T...> {
   func bar(_: T...) {} // Is it now a variadic list of only `T` or all T's?
}
struct S<...T> {
   func bar(_: ...T) {}
}

If there is no ambiguousness for the former form then I guess I can live with it, but it will require some mind-bending, because if you missed the parameter list from the type it's not clear what T... in the function really means until you check the original T.

I think there should a way to differentiate between a variadic argument type and variadic generic parameter list in function declaration.
I propose using four dots for variadic generic parameter list.

struct VariadicGenericStruct<T....> { 
    func function(withVariadicArguments: T...) {  } // This function only accepts instances of T 
    func function(withVariadicGenericParameters: T....) {  } 
} 

I think most of this thread was derailed by this suggestion. Tuples are composed of heterogeneous types, so cannot be expressed by variadic generics.

1 Like

Can you explain why something like let tuple: Tuple<Int, String> = (42, "swift") can't be valid and expressible by variadic generics?

Especially because the generics manifesto has examples like the following:

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

If this was homogeneous extension, then wouldn't it look more like this:

extension<Element> (Element...): Equatable where Element: Equatable {}
1 Like

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.