I don’t think John was pushing for that sort of spelling as-is, nor I. In the original discussions the distinction was made using prefix rather than postfix ...:
func weather<T...>(
for location: CLLocation,
including dataSet: WeatherQuery<...T>) async throws -> (...T)
)
You are severely discounting the problems that the absence of this feature causes. As the pitch explains quite clearly, there’s an entire class of APIs which exist today and cannot be expressed without repetition (and the resultant duplication of metadata).
Yes, that code looks like it should work to me. You're instantiating Factory<Foo, Int, String> (so Built := Foo, Args := {Int, String}), therefore the substituted type of Factory.init becomes ((Int, String) -> Foo) -> Factory.
The type of a pack element is an opaque type whose requirements are the same as the requirements imposed on the pack type parameter.
For example, if you have
func foo<T...>(ts: T...) where T: Collection, T.Element: Equatable, T.Index == Int {
for t in ts { ... }
}
Then the type of t is opaque, but it conforms to Collection, it's Element associated type is Equatable, and it's Index associated type is equal to Int.
That is, it is as if the body of the for loop was its own function with this generic signature:
func bodyOfForLoop<T>(t: T) where T: Collection, T.Element: Equatable, T.Index == Int {}
It's the same generic signature as foo(), except that T is no longer a pack parameter, it's just a regular old generic parameter.
In the for loop though, the type of t is unutterable -- at the implementation level its a scoped opened archetype, which is the same mechanism as the type of an opened existential. Perhaps one alternative is to allow the user to name it, so you can write
for <T> t in ts {
// ... do stuff with t ...
print(t)
// also you could write stuff like this
let firstElt: T.Element = t[0]
// or
print(T.self)
}
The important thing is that the element type of t is scoped to the body of the for loop and it cannot escape.
It's worth noting this is actually invalid unless the generic context in which localVariable appears in has a pack parameter T and a generic parameter U. What you're doing is referencing withTypePacksafter substitution, so perhaps something like
var localVariable: (Int, String, Float) -> Void = justOnePack
As for this, then yeah, the proposed matching rule for function types that is used for computing substitutions would necessarily fail because the function type on one side has more than one pack expansion type.
func withTypePacks(label1 foo: T..., label2 bar: U...) // legal function declaration according to bullet 1
var anotherVar: (T..., U...) -> Void // this is illegal according to the last bullet point, right?
anotherVar = withTypePacks(label1:label2:)
Discord won't let me post more than 3 replies, so I'm also including a response to @DevAndArtist's post:
The syntax to explicitly substitute an empty pack type has to be G<> because G with a generic type means to infer arguments from context.
You mean cases with associated values containing pack expansion types? I think that should be supported, yeah.
The .N syntax is only supported for tuple types and it extracts the Nth "static" element of the tuple. So .2 in the pack expansion type T... is not supported, and .2 on the tuple type (T...) is a compile-time error.
We could however allow for a dynamic indexing operation on pack expansion types. You'd need a dynamic length operation, like foo.count returning an Int. The indexing operation could probably use a different syntax, like a subscript: foo[42], which would do a bounds check at runtime.
I think the for loop in the proposal would lower to getting the dynamic length of a pack and then dynamically indexing each element, but I'm not sure we want to expose it in the surface language. The rule for the type of a subscript into a pack is funny, because it becomes an opened archetype, but then it would have to be scoped to something. With a for loop the scope is clear because it's the body of the loop, but with a subscript, it would need to be scoped to the lexical lifetime of the result expression or something funny like that.
Sure.
No, because the length is not a compile-time integer, it's an abstract shape. You can't utter an abstract shape directly, you can only say that two pack type parameters have the same shape.
That's basically what is being pitched already, except the requirements can be rather complex, involving associated types and so on, and not just described by a single constraint type; see my post earlier in the thread.
My concern has already been addressed, but for clarities sake: I wasn't even talking about generics, I was imagining T and U as concrete types already. I know that generics only further complicate things and prevent you from guaranteeing that those two are different types (which would help distinguishing between two packs of different types regardless of labels).
I am in general more sad (for lack of a better word) for introducing more differences between function types and function declarations. In a perfect world every function should be assignable to a variable of a matching function type. With generics and without. The three rules from the pitch I referred to would introduce another case where that does not work anymore, but I don't have a solution ready.
I guess adding support for labels in function types would, however, at least help in the future if not solve the entire thing completely (it would also help with the same issue we already have with existing variadic parameters that someone above pointed out).
No I mean some ability to project the generic types from the generic type pack into individual cases, not capturing the entire pack into a single case as an associated value.
You can connect each normal generic type parameter manually to an individual case, but it would be extremely beneficial if we could expand / project the generic type pack in a s similar fashion into enum cases. It doesn't have to be provided with the initial pitch, but something as a future direction would be nice.
// abstract snippet
enum G<T…> {
case 0(T.0)
case 1(T.1)
case 2(T.2)
…
}
Something like this could probably be very handy, unless we would need to expand the pack into a tuple. Using a for loop as the only solution seems someone backwards to me.
That's unfortunate. It would be great if we can get this working somehow in the future.
Why is that though? Wouldn't it be technically possible to restrict the shape, similar to how it's possible to restrict the type of a generic parameter? It would be extremely useful to be able to talk about the element count in a concrete way!
Right, I would view this type of static length definition as a shorthand syntax for a fixed length generic parameter list where you'd use a parameter pack for convenience reasons.
It can also partly unblock multiple type parameter packs for types as long as their length are well defined and not ambiguous.
Allowing true Int values can be left for the future language evolution, when we introduce labels and values inside the type parameter list (+ default (Type) values).
It may be technically feasible, but we are deliberately restricting length requirements in this proposal to avoid, among other things, another avenue of exponential type checking complexity. We're also leaning toward removing the explicit length requirement spelling from this proposal and pursuing that separately.
This proposal does not preclude exploring generalized length requirements in the future.
On a macOS US keyboard it's Alt+8, note that on macOS you can open the virtual keyboard and hold dead keys to see what characters are going to be printed.
This is a great pitch! Anyone who has ever tried to nest more than 10 SwiftUI views at the same level in the same view builder will tremendously appreciate parameter packs (if/when the SwiftUI team adopts the language feature, of course)!
I strongly concur with the use of U+2026 … instead of ... (three full stops). It eschews ambiguity with the closed-range operator and, frankly, looks cleaner in a monospaced font. (While we’re at it, with the understanding that it would be a source-breaking change and therefore an uphill battle, we should use … for the existing variadic parameters, too.)
Swift has robust support for the full Unicode character set in source code; let’s take advantage of it! On a Mac, you can type … with Option-; (Option key + semicolon key), which is pretty easy. (I don’t know about Windows and Linux, though.)
If … is rejected, then I would support a different single character to use for the pack expansion syntax. How about +? Would + introduce any additional ambiguity of its own?
On a separate note, I also concur with the commenters who raise concerns about the length(T…) == length(U…) syntax. T.shape == U.shape is less misleading syntactically, and the use of “shape” instead of “length” or “count” adapts more naturally to the potential future direction in which the concept of “pack shape” is formalized beyond a one-dimensional length.