First off, I want to say that I'm heartened by the overwhelmingly positive response this proposal has received both on this forum and others. As an avid Swift fan myself, I'm super excited to share in the community's enthusiasm for a clean solution to the ideas in this problem space.
It's been a couple of weeks since the draft text went out and I want to respond to a few of the ideas that were kicked around in the mean time. (Notably, I'm going to duck the spelling suggestions!)
Explicit Arity Constraints (@Jumhyn)
func allPresent<Haystacks..., Needles...>(haystacks: Haystacks..., needles: Elements...) -> Bool
where forEach(Haystack in Haystacks, Needle in Needles, Haystack == Set<Needle>)
I think this is absolutely brilliant. I wish the syntax were closer to that of an actual sequence instead of involving a new kind of meta-operator, but I also recognize that type-level zip is a tricky thing to spell any way you slice it.
Stylistic Conventions for Sequences in APIs (@Jumhyn)
but in others, it's a singular noun describing the entirety of the sequence ( Tail
, Init
), and in still others we're using just single letter generic names ( T
, U
).
That is my roots showing. I've mixed the C++ convention of pluralizing pack parameter names where I thought emphasizing the pack was most important, but I've introduced the Haskell/Prolog-style singular names where emphasizing the deconstruction of a pack of types was important. You are absolutely correct that nailing down an eventual entry for the style guide is important here. I'll offer one more potential spelling after we get conformances up and running for your consideration:
/// extension<T> (T...): Sequence { /**/ }
func allPresent<Sets..., Elements...>(haystacks: Sets..., needles: Element...) -> Bool
where Sets.Element == Set<Elements>
With that in mind, I lean towards the C++ end of things - plurals where possible - over the Haskell/Prolog end of things.
Type Composition and Sequences (@ensan-hcl)
These are each interesting examples. I believe that the implicit arity constraint you're after is already covered by the following pitch text:
Arity constraints also propagate along direct references to the element type T in variadic position. So, the following is equivalent:
Because there is a reference to both T
and U
in (T) -> U
, they must have the same arity equivalence class.
Does T...
allows zero-length type sequence?
Yes, it must.
However, I think then it is difficult to express some constraints. Consider creating HomogeneousTuple
struct....
I'm wary this construction should be allowed at all. Consider a rough but demonstrative related idea in function space:
func foo<T, U>(_ x: T..., y: U) where T == U {}
// error: same-type requirement makes generic parameters 'T' and 'U' equivalent
However, the point you raise is important: Then, how can we constrain a type sequence to a homogeneous set of types? At the very least you can spell this to get 80% of the way there
struct HomogeneousTuple<T...> where T: FixedWidthInteger & SignedInteger
The remaining 20% will involve relaxing the type concretization restriction. I think that's out of scope for this proposal so I have to leave you in the unsatisfying position of not actually having an answer at the moment!
Could we have some clarification on (or removal of) the word “archetype”?
Yes, the pitch text is drifting into implementation detail territory whenever you see that word... I will bear this in mind for the formal evolution proposal. I could link you to this entry in the lexicon but I honestly think that definition is pretty paltry (for one, "rigid type variables" is the kind of definition text that only a type theorist could love). An archetype is the runtime implementation of a generic value. If you want a much fuller explanation, I very much recommend this LLVM talk instead. Briefly, When you compile a function
func foo<T>(_ x: T) {}
The entrypoint could be roughly translated in C as
void foo(void *x, void *TMetadata)
Where TMetadata
is the archetype value. All it is is a big table of the information you need to manipulate the value pointed to by x
- so tables of functions that define you how to copy and destroy and move x
, etc.
Concretized/Range Arity Constraints (@DevAndArtist et al)
<types(…2) T> // 0 - 2
<types(2…) T> // 2 - infinity
<types(2 … 2) T> aka. <types(2) T> // 2
<types T> // 0 - infinity
I believe these are out of scope. But I will note that I eventually want counting quantification to be expressed structurally if we were to build it:
func foo<T, U, V, Rest...>(_ ts: (T, U, V, Rest...)) // guaranteed to have at least 3 elements.
I don't believe upper bounds are useful to express in a general framework, but I could be convinced with a suitable example API.