Starting to implement Variadic Generics

(Adrian Zubarev) #21

I'm not sure we're on the same page, but for me Sequences... nor T... is a tuple. These are 'just' generic parameter lists of a 1 ... n range. You can create a tuple 'only' if you write either (Sequences...) or Tuple<Sequences...> (the former falls back to the latter as we already have it today with [T] fallback to Array<T>). Also a small note, an empty tuple aka. Void cannot be represented by Tuple<T...> unless we say that T... can also have the size zero, but that would likely create more issues than helping here.

(Andrea Tomarelli) #22

Ah yeah, that was the problem! To me T was already a tuple inside a VG context, but not to you. And when I said:

I was saying exactly what you intended, that T is a group of parameters and another syntax - like (T...) - is its "tuple expansion".

About T... allowing for empty parameters, I'd like it to be true, but I've not thought about the potential issues. Do you have something specific in mind?

Thanks for your feedback!

(Adrian Zubarev) #23

On the first glance I have no usability issues that I can foresee, but I could guess that it might potentially require more language support and would complicate the already complex feature set we're talking about here.

On the other hand some people asked for being able to extend Void or conform custom protocol to it, which would typealias Void = Tuple<> let them do. Furthermore I think we have to be carful on how ExpressibleByTupleLiteral would behave as not every type that can potentially conform to the protocol can or should be representable by an 'empty' tuple.

(Matthew Johnson) #24

Being able to conform Void to protocols is really important (especially Equatable and Hashable).

Another edge case to keep in mind here is the fact that Swift doesn’t actually have single-element tuples. This will probably complicate the design of Tuple (as a nominal type). I even wonder if we would need to make Void a separate nominal type and have something like Tuple<First, Second, Rest...> in order to prevent formation of single-element tuples like Tuple<Int> (because (Int) is actually just Int in Swift). This may be a really bad idea, but I don’t have a better one off the top of my head.

1 Like
(Adrian Zubarev) #25

That is true but only because of the ambiguity the syntax has today.

// This should be just fine because it wouldn't be ambiguous for the compiler anymore.
let tuple: Tuple<Int> = (42)
print(tuple.0)

So my two cents are, keep the source compatibility but allow single element tuples in an unambiguous way like above.

1 Like
(Matthew Johnson) #26

So you're saying we support single element nominal tuples, but the syntactic sugar for tuples would continue to ignore the single-element case? I suppose that might work and would certainly be more elegant.

(Adrian Zubarev) #27

If I got your right then yes. Speaking with an example:

_ = (42)               // Implicitly Int
_ = Tuple(42)          // Explicitly Tuple<Int>
_ = (42) as Tuple<Int> // Explicitly Tuple<Int>
_: Tuple<Int> = (42)   // Explicitly Tuple<Int>
_ = (label: 42)        // Tuple<label: Int>  or something like that
5 Likes
(Andrea Tomarelli) #28

@DevAndArtist I was thinking about the type of an "instantiated" VG, and I realised I do not know how to express it without VG being a tuple all the way up to the declaration:

struct Bag<T> {
  init(_ items: T...) { }
}

// This is Bag<Int>
let bag1 = Bag(1, 2, 3)

// This is Bag<String>
let bag1 = Bag("Hello", "World")

// This is Bag<()> aka Bag<Void>
let bag1 = Bag(())

struct Variadic<S... : Sequence> {
  // Let's assume that `S...` here makes the initialiser variadic but
  // without the same-type restriction
  init(_ sequences: S...) {}
}

// What is the type of this? If VG are always treated as tuples, this
// could be Variadic<([Int], String)>, but what if we do not go down
// that route like we said in the previous posts?
let v1 = Variadic([1, 2, 3], "Hello")


Edit: I'm soooo dumb, maybe I should wait a little bit after work before writing :no_mouth: the type could simply be Variadic<[Int], String>, right?

1 Like
(Adrian Zubarev) #29

My mental model is simple as again it‘s 'just' a comma sparated list of 1...n generic type parameters.

_: Tuple<Int, String, Bool> = (42, "Swift", true)

Your type signature is also valid, but then you need to make it to a single tuple parameter during the init. Remeber that you can always have tuples of tuples. ;)

(David Sweeris) #30

Kinda OT, but during discussions about that change, was there any talk of representing function arguments as a tuple for each type of arg? Like, "one (possibly empty) tuple of normal args, one (possibly empty) tuple of inout args, one (possibly empty) tuple of @escaping args, etc"? I vaguely recall there was some discussion of making the change, but not anything that was said about why (other than that it'd greatly simplify some stuff in the compiler).

(Joe Groff) #31

Making tuples be nominal types would unify some things, but I wouldn't take that direction as a foregone conclusion as to where we want to go, since as you note there are also many benefits to keeping tuples "built-in". I think we could achieve the same goals of making tuples more first class, such as allowing them to conform to protocols, without making them into nominal types, letting us have our cake and eat it too.

(Joe Groff) #32

You would still need some way to describe the interleaved sequence of inout, etc. arguments. The many complexities of function types IMO make them also a poor candidate for turning into nominal types, much like tuples.

2 Likes
(Adrian Zubarev) #33

What cake do we want to eat here? I think nominal tuples are still better as you can provide the same optimizations behind the scene, at least I hope so, and solve more issues regarding tuples and not just conformances to protocols. Here I meant the possibility of explicit disambiguation for the compiler and acceptance of single (labeled) element tuples where it's appropriate to use.

(Joe Groff) #34

There are two key properties of tuples that I think are important going forward for a variadics design, which I think would be difficult to maintain if they were made into nominal types. First of all, as @technicated noted, there needs to be some primitive type associated with packing and unpacking variadics at the value level. If tuples are themselves a struct, but are also the primitive interface to variadic value, there becomes a circularity issue in defining tuples; alternatively, we'd need to invent different primitives that allow Tuple to be constructed, and I'd be concerned that that design space leads to a much more complex design. Allowing tuples to remain primitive, I believe, allows variadics in turn to be simpler, so I think the complexity cost we pay for special-casing tuples pays off in reducing the net complexity of the language.

Secondly, I think it's very important for the ergonomics of variadics that we keep (T) == T in the type system, and I can only think of very awkward ways we could express that in terms of nominal types. There should not need to be a need to distinguish them in the type system if tuples are integrated with variadics, because we know from type context whether a type variable is variadic or not, and therefore whether a value is concretely a single scalar value or a scalar value bound as the single argument to a variadic parameter.

4 Likes
(Andrea Tomarelli) #35

@Douglas_Gregor
While writing and refining my document on Variadic Generics I wanted to start refactoring the frontend as you said in your first comment on this thread.
So, if I wanted to create this GenericParamDecl wrapper class, what should it extend? Should it be an AST node and extend Decl or something else? I guess that having Decl in the name does not mean that something should be an AST node, right?

1 Like
(Andrea Tomarelli) #36

Just so that nobody misses, I've fired the first PR about this stuff! Let's see what I got wrong :joy:

1 Like