[Edit 2] Link to detailed document (status: not completed yet).
The informations contained in the document supersede the contents of this opening post.
Hello everyone, I was reading the Generics Manifesto document and saw the Variadic Generics section. I would like to start a pitch about them!
This post mainly contains elements directly taken from the Generics Manifesto plus some personal thoughts and comments.
Disclaimer: I'm not a native English speaker so my writing could be less-thant-clear in some instances; sorry for that, I'll eventually try to explain anything that is not clear.
Basics: variadic generics are tuples
The document refers to C++ parameter pack and ... (ellipsis) syntax, that I personally do not particularly dislike, and then at the end of the section refers to a tuple splat operator.
I think that variadic generics can be represented as tuples once "instantiated", and there is a similar precedent in the language too with variadics function argument becoming Array
s. I say "I think" because I'm not actually sure that this is what the manifesto suggests - given the iterators example in which it talks about "zero or more stored properties" instead of a tuple of iterators instances.
struct Variadic<...T> {
var (...items): (T...)
}
let v: Variadic<Int, Float, Any> = ...
// `v` is
// Variadic<(Int, Float, Any)> {
// var items: (Int, Float, Any)
// }
This means that a type with variadic generics is actually a type with a single generic parameter of tuple type, whose number of elements depend on the actual usage. This even accounts for the case in which no generic are specified: the variadic parameter is represented by the empty tuple.
In this way one can directly reference any member via tuple access, and can use common members (if any) on the compound value itself.
In the previous example one cannot use any member on items
, because T
is unconstrained and in Swift there are no members common to all types. But if the declaration was T: Collection
you could use first
on items
and obtain a tuple containing the first item in each sequence (if any):
struct Variadic<...T: Collection> {
var (...collections): (T...)
}
let v: Variadic<[Int], [Float], [Any]> = ...
// `v` is
// Variadic<([Int], [Float], [Any])> {
// var collections: ([Int], [Float], [Any])
// }
let result = (v.collections.first...)
// result has type (Int, Float, Any)
Pattern matching
The Generics Manifesto uses the following example:
var (...iterators): (Iterators...)
[...]
guard let values = (iterators.next()...) else { // call "next" on each of the iterators, put the results into a tuple named "values"
reachedEnd = true
return nil
}
If the syntax (variadicPack.someMember...)
means get someMember from all the items of variadicPack and put them, in order, in a new tuple, then (iterator.next()...)
if actually of type (Element1?, Element2?, ..., ElementN?)
and this is not an optional that can be used in a guard
statement. So maybe I'm missing something from the document or we might want to bless the ... operator with some special meaning regarding optionals (i.e. the result is nil
if any element is nil
, otherwise it is a tuple of non-optional elements).
If giving special meaning to ... for optionals is not desired, something like the following might be more appropriate:
switch (iterators.next()...) { // switching over a tuple of N optional elements
case (.some(let v)...): // matching the case of all elements being non-`nil`. The variable `v` contains all the elements
return v
default: // default case: at least one element is `nil`
reachedEnd = true
return nil
}
Tuple splatting
We could use the same ... operator for tuple splatting. Other than the example of the Manifesto, in which a function with variadic generics is called expanding the arguments passed to it, the feature might be useful to allow one to write something like this:
let (...head, tail) = someTuple
// `someTuple` is a tuple containing N elements
// `head` is a tuple containing the first N-1 elements of `someTuple`
// `tail` is a single value (not a tuple) containing the last element of `someTuple`
//
// If `someTuple` contains only one element, `head` will be the empty tuple and `tail` will contain such element
// This is not possibile in current Swift because single-element tuples are not allowed
//
// If `someTuple` is empty ww might want this code to not compile and raise an error
What do you think? Any thought about the implementation level of this? Let's start the discussion!
edit: link to previous discussion.
[Edit 2] Link to detailed document (status: not completed yet).
The informations contained in the document supersede the contents of this opening post.