Another attempt at passing arrays as varargs (with implementation)

Many past conversations around passing arrays as variadic arguments have pitched using a leading or trailing ... as a 'splat' operator instead of a heavier-weight expression like as T.... These operators read clearly at the call site, but they conflict with the existing partial range from/through operators in the language. as T... has the advantage of avoiding these conflicts, and it's expected that this feature will not be used pervasively throughout a codebase, which helps justify a more verbose spelling.

Can you inline the "the existing partial range from/through operators in the language" examples on the proposal? It is not clear to me how the conflict. Semantically? Mental model of the concept?

@masters3d you may have missed this post which calls it out as already compiling to PartialRangeFrom<_>.

For my own understanding, is there any chance of making the trailing ellipses operator f(x...) work from an ABI compatibility standpoint (not a spelling preference)?

1 Like

Sure but how does that stop us from using it as a splat or spread operator? Spread syntax (...) - JavaScript | MDN

let numbers = [[10], [20],  [10]]

print(numbers[1...]) // [[20], [10]] // Current swift
print(numbers[...2]) // [[10], [20], [10]] // Current swift
print(...numbers) // [10], [20],  [10]  // Future swift should be okay
print(numbers...) // [10], [20],  [10]  // Future swift should be okay

I don't think a postfix ... syntax would have ABI impact, no.

If someone has defined a postfix func ...<T>(value: [T]) -> SomeType operator, it would become uncallable. I doubt anyone has done that and I don't believe we consider this kind of change to violate our source compatibility guarantees.

It would require a somewhat more involved change to the expression typechecker, though—any time someone used a postfix ..., it would need to treat splatting an array as a potential overload.

2 Likes

Thanks for the explanation, I appreciated that.

So if that's the case, @owenv would you be willing to use a trailing ... as a spread operator? Or could it at least be called out as a future direction as a shorthand for as T...?

I think for now I'd prefer to stick to as T..., mainly because it would be potentially confusing to introduce a second use for the ... operator with a very different meaning. Especially since one use would be a traditional operator and the other would be 'compiler magic'.

I've got a couple of other very minor concerns as well:

  • A couple times in the past, the idea of conditionally conforming Array to Comparable using lexicographic comparison when the element type is Comparable has come up. We wouldn't be able to ever consider that if we use ... for varargs expansion.
  • It's hard to say what the impact on type checking would be if we introduced a new disjunction for every existing use of the range operator. It might be fine, but it could make some expressions in existing code too complex to solve

That being said, I think the proposal as it's currently written is a little too dismissive of this syntax, so I'll see if I can revise it to explain the tradeoffs a bit better.


Edit: I revised this part of the proposal, still open to feedback on how it could be made better though!

2 Likes

If implicit conversion isn't a feasible option then as T... definitely makes the most sense to me to convert between the basically identical T... and [T] types. There is already implicit conversion from T... to [T], e.g.:

func f(_ x: Int...) { let y: [Int] = x }

so if we can't have implicit conversion from [T] to T... then explicit conversion using type casting is the natural next step. Of course, given that there are trivial conversions in both directions here, you're left wondering why they were ever treated as separate types rather than just a syntactic convenience feature for call sites, but you can't change history.

2 Likes

We only have one instance in the standard library where the same spelling is used for two semantically distinct operators: the string concatenation operator + and addition operator +. This has been noted several times on this list to be a mistake that's too late to correct.

I would be reticent to see another such issue deliberately created with ..., and I've yet to see any reason to prefer it over another very common spelling for splatting, unary *. At least with the latter spelling there isn't already a unary operator in the standard library that's spelled the same way.

That said, even with unary *, it is (or was) the plan of record to use an operator for tuple splatting. I think it's an open question whether we want to use the same spelling for the much more restrictive (in the sense that it requires homogeneous arguments, and in the sense that it'd be used specifically to forward as a variadic argument) array splatting. Doing so now could preclude an elegant syntax for explicit tuple splatting (which I very much hope will come back).

This is all to say that even if there were no type checking performance problems, I can see several reasons why ... or even another operator would have drawbacks here.

3 Likes

Oh, I didn’t know that. Could you link some discussion regarding this?

The updates you made to the proposal look great. Thanks for including that paragraph. I'm all on board for as T.... Really looking forward to seeing this progress further.

That's already valid Swift code and it returns a prefix/postfix ArraySlice using the passed range. Are you saying you want to somehow automatically convert that to a variadic?

updated.

let numbers = [[10], [20],  [10]]

print(numbers[1...]) // [[20], [10]] // Current swift
print(numbers[...2]) // [[10], [20], [10]] // Current swift
print(...numbers) // [10], [20],  [10]  // Future swift should be okay
print(numbers...) // [10], [20],  [10]  // Future swift should be okay

I think I could potentially get behind a postfix numbers..., although I do worry that it's spelling is too similar to Ranges.

What's the case for the prefix ...numbers? If I encountered this in wild Swift code I don't think I would be able to intuit the expected behavior. At least the postfix looks variadic.

The core team discussed this pitch today. We think it is solving an important problem, but doesn’t sufficiently account for future design directions, such as variadic generics, which should—when introduced—work like it’s part of the same feature. The proposed syntax is specific to homogeneous collections (so wouldn’t apply to generic argument packs) and needlessly so: the compiler doesn’t need the element type to be restated just so it can explode the array. We’re not necessarily opposed to adding array explosion problem before full variadic generics are introduced, but how the designs work together, and some evidence that we’re not painting ourselves into a corner that precludes a desired future, should be part of any proposal

Thanks all

16 Likes

I mentioned it above as the spread operator in JS. I am more confortable with the prefix as the spread operator.

Thanks for the update @dabrahams, the core team's feedback sounds reasonable.

I'm going to spend some time thinking about how we might move forward with a design which would be consistent with future language features. However, I'm not sure there's currently a detailed enough design for variadic generics to guarantee we could come up with a compatible syntax, so it may be best to shelve this pitch for awhile and return to it once that feature has landed.

Given @dabrahams's feedback, specifically:

It seems as if the ... operator should be given another look. Reading through the Variadic Generics section in the Generics Manifesto and the ... operator is called out specifically as a possible spelling for the feature.

Variadic generics would allow us to abstract over a set of generic parameters. The syntax below is hopelessly influenced by C++11 variadic templates (sorry), where putting an ellipsis ("...") to the left of a declaration makes it a "parameter pack" containing zero or more parameters and putting an ellipsis to the right of a type/expression/etc. expands the parameter packs within that type/expression into separate arguments. The important part is that we be able to meaningfully abstract over zero or more generic parameters...

It places the ... operator before the type name, which falls out of alignment with the direction things were going in this thread. But! I came across this comment by Douglas later showing some preference to ... as a suffix:

I think this is fuel enough to reconsider the spelling of this pitch's feature. The ...operator (as a suffix) seems like a great way to package up all of Swift's variadic features.

3 Likes

Seems like postfix ... might be the way to go!

2 Likes

Postfix ... might work, but it's not clear to me how using it with variadic generics would maintain source compatibility of the existing range operator. Without a more detailed design for 'generic parameter packs' and where they're allowed to appear, I'm not sure it's possible to guarantee we wouldn't be painting ourselves into a corner compatibility-wise. I'd be happy to be proven wrong, I'm just a bit skeptical.

Hard to be proven wrong by a feature which doesn't yet exist that uses undefined syntax... :grimacing:

1 Like