I think this is a fantastic proposal, and the right first step toward variadic generics. The care and level of detail provided in this proposal are exceptional, and I feel like it's hit the design sweet spot for such a challenging feature.
I have some quibbles with a few details of the proposal, which I'll get to below, but they are all minor.
It is. I included variadic generics in the "Generics Manifesto" years ago, back before we had a process for vision documents, because it's important kind of abstraction we're missing from the language. Folks end up writing lots of overloads based on arity, stamping them out manually or with external-source-generating tools, which is quite unfortunate. Macros will make some of this easier, but it still won't be a good state.
Yes; we've been trending toward this for a while. The syntax proposed here---with repeat
and each
fits well with the way we've been introducing similar type-system features (some
, any
, borrowing, etc.).
I'm the original designer and implementer of C++ variadic templates, which are the most similar language feature to this. The design proposed here is better and more comprehensive than the direct "port" of variadic templates into Swift that I had previously been thinking of.
Read through the proposal and earlier iterations, light involvement in pitches, and watched the implementation from the sidelines.
Now to my actual commentary...
Elision of repeat
for generic requirements
Early in the proposed solution, there is this exemption from the need to write repeat
for generic requirements:
A pack expansion consists of the
repeat
keyword followed by a type or an expression. The type or expression thatrepeat
is applied to is called the repetition pattern. The repetition pattern must contain pack references. Similarly, pack references can only appear inside repetition patterns and generic requirements:func zip<each S>(_ sequence: repeat each S) where each S: > Sequence
I don't see any reason why this should be the one place where we allow the elision of repeat
. It's inconsistent, and it feels like it's going to trigger ambiguities when there are local generic functions:
func f<each T>(_) {
g(repeat {
func h<U>(_: U) where each T == U
})
}
For the nested functions inside that closure, the proposal means that all of the T
s == U
, because there's an implicit repeat
for the generic requirement, but perhaps I meant that only the current T
is equal to U
. I can't express that with the proposal now, but I think I should be able to. Making the repeat
explicit for generic requirements, like it is everywhere else, improves expressiveness and eliminates an unnecessary special case.
There is a small writing thing in the proposed solution where we introduce pack expansions that tripped me up
The syntax for a pack expansion type is
repeat each P
, whereP
is a type containing one or more type parameter packs.
Written asrepeat each expr
, whereexpr
is an expression referencing one or more value parameter packs.
In both cases, the each
isn't part of the syntax of pack expansions: the syntax is repeat P
or repeat expr
, respectively, and there's a semantic constraint that somewhere in the type or expression (or generic requirement, per my comment above) that there be at least one each
in there that isn't covered by another repeat
.
each
and parentheses
We will refer to
each T
as the root type parameter pack of the member type parameter packs(each T).A
and(each T).A.B
.
The required parentheses here are annoying. I feel like we've been through this before with (any P)?
and regretted being pedantic. Could these rules be relaxed in some way, so that each T.A
and each T.A.B
would be acceptable?
If we get this simplification, we'll have to decide what it means if some day we get associated type parameter packs, where any of T
, A
, or B
could be packs. I'd be fine with .each X
indicating a nested parameter pack that's getting expanded, e.g., T.each A.B
for cases where B
is the pack that's being expanded.
Value parameter packs
Minor nit, but this one-off sentence before the "capture" discussion
Note that pack expansion expressions can also reference type pack parameters, as metatypes.
reads as if the only use of a type pack parameter in a pack expansion expression is as a meta type; that not true, since there are other syntactic constructs one could use such as as? each T
that aren't metatypes. I suggest either generalizing this sentence or striking it.
Overload resolution
There are other replies about the overload-resolution behavior, so we need not belabor the point here, but I find the fact that this is to be ambiguous:
func overload<T>(_: T) {}
func overload<each T>(_: repeat each T) {}
overload(1)
to be surprising. Usually, I've tried to describe the overloading rules as "if the parameters of A can be forwarded to B, but the converse is not true, A is more specialized than B". That hints that the fixed-arity version is more specialized than the variadic version, which also makes intuitive sense. (It's the same, but for just one argument)
Macros!
Value parameter packs are defined thus:
- A value parameter pack is a function parameter or local variable declared with a pack expansion type.
but we also want macro parameters to be eligible to be value parameter packs.
Future directions
I'm very sad to see pack iteration subsetted out of this proposal. I understand the complexities here, but this feels like one of those places where we can take a very difficult feature and make it accessible.
Doug