Variadic Generics

I think that it should be the type to which you constrained the VG or Any if no constraint was given:

struct NonConstrainedVariadic<variadic Ts> {
  let ts: Ts

  func test() {
    for elem in ts {
      // The type of `elem` is `Any`
    }
  }
}

struct ConstrainedVariadic<variadic Cs: Collection>
  where Cs.Element == Int {

  let cs: Cs

  func test() {
    for elem in cs {
      // The type of `elem` is `Collection` (of `Int`s)
    }
  }
}

Is this wrappedValues necessary? In what situation would it be superior to the existing map?

Only to be consistent, I guess... I thought it would be weird that you can directly access all members of the base type but some (i.e. those shadowed by Collection).

But maybe we should not include this dynamic member access at all and only rely on map at this point?

I know it has been a while since this thread has been active. I just wanted to ask if you have any update on the state of your variadics generics proposal?

Also, I want to commend you on the hard work that has already went into this so far, though I do not possess extensive knowledge regarding the internal workings of the compiler, I have read enough about variadic generics in Swift in anticipation of this feature to know about the extreme complexity required to implement such a major change to the Swift generics model, so ya... keep on keeping on :wink:

5 Likes

TL;DR No, as of today I have not made any progress proposal-wise or implementation-wise.

Long answer: unfortunately I had health issues, I had (have) a disk herniation that blocked me from doing anything, even the most basic tasks, because of the pain. These days I am feeling better and I'm event thinking to go back to my work a couple of days before the Christmas holidays, but the full recovery is still going to take a long time.
Moreover, me and my girlfriend (sounded so childish :sweat_smile:) partner were thinking to move together but obviously this task had to be deferred because of my health problem. But the time has come, and from a couple of weeks until the end of the holidays (at least) we're going to be working on this.

So sad as all of my problems are, there actually are some news on VG's.

I have been following all the new topics: SwiftUI, View Builders, Generics UI improvement, etc. The existence of Group in the new UI library is yet another evidence of the need for this feature, and the new advances in Swift might help us better implement VG's.
What I can say is that in this span of time I've been thinking of a new way to represent Variadic Generics "inside" the generic scope, one that can better integrate in how Swift works, and I hope to clean my mind and update the document during the holidays (but, no promises!).

One more thing: is Apple "secretly" (i.e. internally) working on Variadic Generics, due to the lack of updates on this topic? Maybe someone like @Joe_Groff can give us some hints about that!

18 Likes

I thought about variadic generics recently because it was anounced that Swift 6 will have this function at here.

I found this thread and read draft of proposal.
Then I want to improve expression of curry.

One existing idea is this.

func curry<A, B, C, variadic D, Result>(_ fn: @escaping (A, B, C, D...) -> Result) -> ???

It seems to infer type of ???.
But it is not appropriate for Swift which always describes signature of function.

Another is this.

enum CurryHelper<variadic T, Result> {
#ifempty T
  typealias Fn = Result
#else
  typealias Fn = (#head(T)) -> CurryHelper<#tail(T), Result: Result>.Fn
#endif
}

I think its good for point of that it is succeeded to express.
But rule of evaluating #ifempty happens when application of generic parameters is not consistent with existing evaluation of #if.
#if is evaluated statically only once if it is used in declaration like enum.
This is confusing behavior for programmer.

So I propose #condtype as a new operation to represent branching in compile time.
It can be expressed as below with this.

enum CurryHelper<vairadic T, Result> {
  typealias Fn = #condtype(#ifempty(T), 
    Result,
    (#head(T)) -> CurryHelper<#tail(T), Result>.Fn)
}

Or we can eliminate namespace by CurryHelper.

typealias CurryFn<variadic T, Result> = #condtype(
    #ifempty(T), 
    Result,
    (#head(T)) -> CurryFn<#tail(T), Result>
)
1 Like

Lo and behold! The new revision of the document is ready!

@omochimetaru unfortunately, at least for the moment, I've removed the "support" for curry from the proposal...

10 Likes

Bike-shedding ahead.

The "variadic" part should precede the rest of the formal parameter declaration. That way, when using a parameter without a constraint, we use a "variadic T" instead of the more awkward "T: variadic Any". (Although your code uses the latter, the formal grammar description uses the former.) After all, it is the "T" part being multiplied, not the constraint.

4 Likes

Agree. I haven’t made it through the whole revised draft yet, but this jumped out at me in the bit I did read. It took me a while to figure out how unconstrained variadics were intended to be spelled. Is there a reason for the current design placing variadic before the constraints instead of before the type parameter?

2 Likes

Some thoughts on the Motivation section and examples based on a quick skim:

  1. It would be more compelling if SwiftUI was given greater prominence (and it was illustrated how variadic generics would solve the problem).

  2. The example for comparison seems weak IMO. For that particular case, the easier solution (without adding any language features) would be to have a 3-way comparison type and extending Comparable with a compare operation that returns a Comparison. One can then combine Comparisons with the natural (left-to-right) monoid operation.

@CTMacUser, @anandabits
In past revisions of the document I've put variadic before the generic parameter name, but in this revision I moved it to (try to?) make it more similar to some and the unreleased any. This decision is obviously not set in stone, and T: variadic Any seems rather awkward to me, too! But I thought that - in general - you will not have unconstrained Variadic Generics, so the "movement" might be worth the awkwardness...

@typesanitizer I started to write the document long ago, before SwiftUI was unveiled, and I wanted to release this update as soon as possible. The SwiftUI section might be updated in a future revision, and why not with the help of Core Team members!

Moreover, IIRC the comparison example was taken from a thread here in the Swift Forums, and that thread mentioned Variadic Generics (again, IIRC). I've almost not updated nor thought about that section or other possibile solutions since the first drafts of the document I think...

variadic seems like a very different modifier than some / any to me.

For instance consider this example:

 T: some Codable
 U: any Codable

The modifier here relates to how T or U relates to Codable.

However in this example:

<V: Codable, variadic W: Codable>

The variadic keyword would not pertain to the nature of the conformance, it relates to what type of entity W is.

10 Likes

Mmmh yeah, in the end I think this was a failed experiment!
I think I’m going to update the document to restore the old version of the keyword.

3 Likes

@technicated thank you for your time and effort in this but I personally have lots of problems with that design and from my own view point variadic generics must be build with some sort of static metaprogramming in mind. I don‘t want to sound bad but I think the only things that I agree with is the spelling variadic T and the un-packing into a tuple T.... Everything else makes my head spin.

By the definition of the proposal this should be valid:

struct S<variadic T> {
  let t: T
  init(_ t: T) {
    self.t = t
  }
}

let s_1 = S(42, false)
s_1.t // this should NOT be a collection, so there is no `map` operation here!
s_1.t.0 // this is not valid `variadic T` is not a tuple `T...`

let s_2 = S() // this seems to be equivalent with an empty tuple

We could potentially redefine Tuple as a variadic struct, which was likely mentioned upthread, but it would require two more things.

  • A way to unfold/unpack variadic T into statically safe boilerplate template
  • Generic type parameter labels

That is only needed for tuples. We would shouldn‘t forget about enum cases as well.


Brainstorming:

struct Tuple<variadic T> {
  init(_ pack: T) { ... }
}
postfix func ... <variadic T>(_ pack: T) -> Tuple<T> {
  Tuple(pack)
}

func foo<variadic T>(_ pack: T) -> (T...) {
  pack... // calls the above function
}

typealias Void = Tuple<>
2 Likes

Don't worry if you don't agree, this is a delicate subject and worth many discussions!

I'm not sure how to interpret your first code snippet: in this revision of the document where variadic values are Collections and not tuples, s_1.t is a Collection, so you can use map - what do you mean by "this should NOT be a Collection"? Do you prefer t not to be a Collection, or the proposal does not make clear that t actually is a Collection?
Moreover, s_2 is not an empty tuple but a struct whose only member t is some Collection.


Early revisions of the document used tuples and less "dynamism" as the model for Variadic Generics, then someone upthread mentioned that we could directly make variadic values real collections, so I tried to change the document to reflect this suggestion - but we can recover, update and prefer any past revision.

I'm not sure I prefer the Collection approach, but it seemed to solve some issued I had with past revisions and some things I didn't like about them. It was worth a try I think, but maybe there is a third way!

Well I only speak for myself and not for others but I would not want variadic T automatically conform to Collection<.Element == Any, ...>. I think I would prefer this to be a new standalone type which shares some similarities with a tuple, yet itself isn‘t a tuple. We could probably refer to its values as 'packs', while what it can and cannot do still needs to be defined. I think that such packs and its type variadic T should be build on top of static guarantees. The only issue is that if we want do something useful with it we either need to transform the pack into a different type, like for example unfolding it into a tuple, or we need some kind of static metaprogramming to tell the compiler to generate statically safe code by iterating over the pack‘s type and values.


In my above example s_2 would have an empty pack, therefore it would be fairly similar to an empty tuple, which should make them convertible. That‘s all I was trying to say with that particular example.

mmmmh, this is the key I guess... In Swift we have a very powerful type system and it will be a waste not to use it for this feature. And I think I like better the idea of Variadic Generics friends of tuples more than Collection...

We might also want to seek some guidance from the Core Team!

1 Like

Definitely from @Douglas_Gregor as he co-authored this paper: http://www.jot.fm/issues/issue_2008_02/article2/

But right now he seems to be very busy with function builders, so no idea if variadic generics could be pushed for the next major Apple OS release cycle or not. I would love to see it, even if we could design a roadmap for the final design but start with something simple which would solve some of the common problems we hit in many frameworks today.

2 Likes

I have finally had a chance to get through more of the new draft. Thank you for continuing to work on this very important feature! You've written a lot and I still haven't had time to get through all of the detailed design.

A variadic value can also be passed as the variadic argument of a variadic function (yay!).

Since the members of a variadic value may have different types this will not always be the case. The proposal should probably spell out more clearly exactly when this is expected to work.

// The name of the generic parameter can be used to specify how the
// specialization should be done; note that in this case all the labels are
// specified for clarity but not all of them are really required
let vg3: ComplexVariadic<A: Double, B: Int, T: Int, String, U: String, String>

I was surprised to see generic argument labels get introduced in a comment in the detailed design. This is an important topic that is independent of variadic generics. It will be useful for disambiguation in variadic generics but should probably be an independent proposal.

When we do introduce this feature I think we should consider supporting distinct argument labels. The name you want to introduce into the generic context may not always be the name that makes sense in label position.

+1


Variadic sum types

One very important use case that isn't discussed at all is variadic sum types. Maybe this is because Swift doesn't have structural sum types at all yet. But it probably should (sum types should be on equal footing with product types) and variadic generics are maybe the right place to motivate this topic.

In the document you give an example of the combineLatest operator. In your example, a transform is used to combine the elements. An obvious default transform would be to simply return a tuple. In fact, the combineLatest operator in ReactiveSwift just does this instead of taking a transform. Because tuples are friends with variadic values this tuple-producing operator will be easy to implement in a variadic fashion.

Another variadic operator that might be interesting to have a is a heterogeneous merge operator. In order to implement this in a variadic fashion we would have to have a structural sum type* that is "friends" with variadic values. We cannot rely on the workaround that uses a nominal sum type without also losing the ability to use variadic generics. Having to both introduce a nominal sum type and write out the variadic boilerplate manually will usually mean this operator is just omitted from a library. That is an unfortunate consequence that I hope we can avoid.

4 Likes

+1 I want a variadic Union type, which would also likely push typed throws forward.