Variadic Generics

I also only now realized that I did not invite @Austin to join this thread - I apologise! If you are still interested in this area I'd love to hear your comments too!

The YYYY document now contains details about the for ... in syntax and some craziness with compile-time helpers and recursive Variadic Generics (Curry example)... I guess it's a start!

1 Like

Should there be a "#countOf" function on a variadic pack to extract the length of the pack? It should be @constexpr whenever possible. Should we require @constexpr support first before adding the count operation? I'm not sure when it would be useful for now, though.

I feel like we need support of some sort to disambiguate a foo<T...>(args: AGenericType<T>) between being args: AGenericType<A, B, C> or args: AGenericType<A>, AGenericType<B>, AGenericType<C>.

For example, from your document:

extension Array {
  func sort<T: Comparable>(_ sortProperties: KeyPath<Element, T>...) {
    [...]
  }
}

We must have a way to express that sortProperties is actually 'a variadic list of KeyPath types, one for each type in T', like telling the compiler to map each T in T... using a partial type function * -> KeyPath<Element, *>, resulting in the final variadic type of sortProperties. Otherwise it could look ambiguous to the compiler, and the user:

extension Array {
  func sort<variadic T: Comparable>(_ sortProperties: KeyPath<Element, T>...) {
    [...]
  }
}

Is sortProperties an array of KeyPath<Element, T1, T2, T3, ...>, or one variadic type KeyPath<Element, T1>, KeyPath<Element, T2>, ...?

I feel like it is important to support expressing both of those when the user sees fit:

// 1.: A list of generic types specialized with each T:
extension Sequence {
  mutating func assigning<variadic T>(keyPaths: WritableKeyPath<Element, T>...,  values: T...) -> [Element] { // detect variadic `T` and interpret ellipsis as many specializations of `WritableKeyPath`s? I dunno about this one...
    [...]
  }
}

people.assigning(keyPaths: \.name, .\age, 
                 values: "John Smith", 26)

// 2.: One type specialized once, with the whole variadic list:
func toArray<variadic T>(_ zip: ZipCollection<T...>) -> [T...] { // ellipsis in type parameter list could mean "splat"
    [...]
}

toArray(someZipCollection) // (ZipCollection<Int, String, Double>) -> [(Int, String, Double)]

(This is just a weak shot at a syntax (and use cases) to illustrate the point, btw).

Thank you both for your feedback!


@CTMacUser yeah, I also think that an utility to get the number of elements of a variadic item might be useful, event if we do not have @constexpr. I'm also not sure if, in some dynamic cases, the element count could be evaluated at compile time! So maybe no need for @constexpr at all.


@Luiz you are right, and I thought I outlined it in the document! If this is not the case, it's possibile that I wrote about this potential issue in the second document, an updated copy / version of the first one that I'm working on to update stuff!

That aside, my idea is that we should simply not allow the mixing of VGs and variadic functions, in order to avoid confusion and unnecessary complexity. I could not find a scenario in which it was useful to combine the two things, but that my just be me!

This means that, in my opinion, a compiler error should be raised in the following case:

func aVariadicFunction<variadic Args: SomeEventualProtocol>(_ args: Args...) {
  [...]
}
// error: Variadic generic argument do not require the use of `...`. Remove it.
// func aVariadicFunction<variadic Args: SomeEventualProtocol>(_ args: Args...) {
//                                                                     ^~~~

Given SwiftUI's ViewBuilder:

static func buildBlock<C0, C1>(C0, C1) -> TupleView<(C0, C1)>
static func buildBlock<C0, C1, C2>(C0, C1, C2) -> TupleView<(C0, C1, C2)>
static func buildBlock<C0, C1, C2, C3>(C0, C1, C2, C3) -> TupleView<(C0, C1, C2, C3)>
static func buildBlock<C0, C1, C2, C3, C4>(C0, C1, C2, C3, C4) -> TupleView<(C0, C1, C2, C3, C4)>
static func buildBlock<C0, C1, C2, C3, C4, C5>(C0, C1, C2, C3, C4, C5) -> TupleView<(C0, C1, C2, C3, C4, C5)>
static func buildBlock<C0, C1, C2, C3, C4, C5, C6>(C0, C1, C2, C3, C4, C5, C6) -> TupleView<(C0, C1, C2, C3, C4, C5, C6)>
static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7>(C0, C1, C2, C3, C4, C5, C6, C7) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7)>
static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8>(C0, C1, C2, C3, C4, C5, C6, C7, C8) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8)>
static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>

variadic generics seems like one of the most pressing inclusions for Swift. I can't imagine shipping iOS 13 with some arbitrary limit on the number of views, and I wonder what it will mean for compatibility to have variadic generics replace these methods in the future. If we have an iOS 14 app with variadic generics will we be limited to 10 views if we want to support iOS 13?

To the core team, I'm asking if among all the many things to prioritize for the Fall release, can we commit to solving this too?

10 Likes

I've read (although not in depth) the SwiftUI and Function Builders threads, and I'm thrilled by them!

I'm still working on the Variadic Generic document too, even if had some issues with my time management. I think I can submit a revised version of the document in not-too-much time.

7 Likes

A refined version of the document is finally out, sorry for the wait!

Among all the updates, I've cleaned the document out and addressed the Variadic Generic / tuple "conflict", clearly stating that they are two separate entities and suggesting the usage of the ... and (v...) syntax to explicitly perform a conversion between the two things.

EDIT: Looks like I can't update the opening post, so I cannot link it to this updated version of the document. Beware of that!

9 Likes

I didn’t read everything in detail but I did notice what looks like one mistake:

func reduceVariadic<variadic T: P2>(_ values: T) -> Double {
  return #reduce(values, 0) { (carry: Double, item: P2) -> Double in
    // Note that the `reduce` below is that of `Array`!
    return carry + item.getAssociatedValues().reduce(0, +)
  }

P2 has an associated type so it isn’t valid in type position in the reduce call (yet). It isn’t clear to me what you intended here. It looks like maybe you intended the closure to itself be generic such that the second parameter has a potentially different call in every iteration of the reduction. That’s obviously not possible in Swift today.

Nice work on this! It's nice to see the detailed specification, and the inclusion of features like iteration support and #reduce. I think an implementation of this design in its current state of this design could be used in powerful ways.

@anandabits nice catch! You are right, I really didn't notice this error.

Of course the definition of #reduce can be changed to only accept a reference to an existing function, so that one could use it in the following way:

func reduceHelper<T: P2>(carry: Double, item: T) -> Double {
  let associatedValuesArray = item.getAssociatedValues()
  return carry + associatedValuesArray.reduce(0, +)
}

func reduceVariadic<variadic T: P2>(_ values: T) -> Double {
  return #reduce(values, 0, reduceHelper)
}

Of course this will prevent an inline closure to be used, but maybe the limitation can be lifted and support for this added once something similar to opaque result types can be used for arguments.


@Spencer_Kohan thank you!

Unfortunately in order to implement this stuff some "maintenance" work needs to be done on the compiler first. I've started the first PR about this, but it has not been reviewed yet.

1 Like

It should solve Combine's framework issue as having Publishers.Merge/Merge2/Merge3.../Merge8, shouldn't it? I think is has to be included in the next release and Combine has to change it during the beta

6 Likes

Yeah, variadics would help with CombineLatest, Merge, Zip, and many others. I hope we see it sooner rather than later.

14 Likes

Similar issue with SwiftUI functional builder. Now we have a limit of max 10 items inside View body. The function declaration leaves something to be desired https://developer.apple.com/documentation/swiftui/viewbuilder/3278693-buildblock. I do believe all of that is going to be changed by the release

5 Likes

Do we expect any API changes in next version?

Are you asking if Variadic Generics are going to be included in Swift 5.1 (or maybe 5.2)? If so, I think the answer is absolutely no, because I'm still working on a prototype implementation of the feature.
Unfortunately this proto implementation is not going to be ready soon, because there are some areas of the compiler that I still have to explore and understand.

5 Likes

With the recent introduction of function builders and SwiftUI, we've seen that Swift is starting to feel the lack of variadic generics.

Maybe the core team has a (tentative) timeline for the completion of the features cited in the Generic Manifesto ?

The core team already mentioned that SwiftUI does indeed push us so that we consider implementing this feature sooner.

Now Combine framework started coming up with new strange types which wouldn't exist if we had variadic generics and SE-0249 implemented.

https://developer.apple.com/documentation/combine/publishers/mapkeypath3

4 Likes