Another attempt at passing arrays as varargs (with implementation)

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

15 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!

1 Like

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

I think a generic parameter pack would probably be some new kind of structural type. You certainly can't apply postfix ... to those today, so I think the main question is, would we want to use pack... on a pack whose elements were known to conform to Comparable as "map each element of the pack to its PartialRangeFrom"? That seems a little more magical than is typical for Swift—wouldn't we want to somehow say map explicitly so the we could instead specify some other operation, like reduce?

(I suppose a generic parameter pack could instead be modeled as some sort of exotic tuple, and if it was, that would interfere with conforming tuples to Comparable. But given that we only recently won the war with 1-tuples, I'm not sure how that would work...)

It was mentioned earlier that 'splat' became a term of art because the asterisk resembles a splat could we use *** as an operator to avoid collision and maintain some amount of symmetry?

1 Like

I suppose in that case it might be possible to have effectively three overloads of ...:

  • The existing one with a comparable constraint
  • A new one constrained to Arrays
  • And yet another sometime in the future, with a layout constraint for parameter packs, assuming they're a new kind of structural type.

In that case, I think we could assume there'd be no operator overlap without overly restricting future design decisions. Maybe the latter two overloads could be functions in the standard library with custom compiler-defined behavior to avoid the need for lots of special casing in the type checker?

If the goal is for this feature's syntax to be in line with that of variadic generics, it still feels backwards to me to design the smaller, less complicated feature first. But, if we can do it without making too many harmful assumptions, then maybe it's still worth it.

Personally, I think the asterisk(s) invokes pointers of old which may be hard to depart from. I feel like the triple asterisk is pretty visually heavy.

1 Like

How about postfix *..?

Although T... seems to have been ruled out now, I'd like to comment on it anyway. One drawback of writing the type explicitly is that you … have to write the type explicitly. There's a conceptual load in making sure you write the right thing.

For example, if you have this:

let a = [0, 1, 2]
… a as Int... …

you have to figure out that Int is the correct type. If you realize that you want this instead:

let a = [0, 1.1, 2]
… a as Double... …

then you annoyingly have two changes to make.

Personally, the alternative syntax I'd prefer, out of the things suggested in this thread, is:

 `a as ...`

where ... is just a sigil, not a postfix operator. If you think in these terms:

 `[0, 1, 2] as ...`

there's a nice internal anti-symmetry.

FWIW

3 Likes

Something like this (i.e. as ...) could also just be shorthand, similar to how you can write

let s = [1, 2, 3] as Set

and Set<Int> is inferred, but you can also write Set<Int> explicitly. This is all complicated by T... not being a real type, so you don't get a perfect analogy, but it roughly works conceptually.

1 Like

Hi all,

I'm working on an overhaul of this proposal to take variadic generics and other future features into account, but wanted to make an attempt to resolve some of the conversations around syntax first.

In my opinion, the best option at this point is to introduce an operator for the splat/spread operation. An operator will eliminate the need to restate types, and can work consistently for homogenous arrays and heterogenous parameter packs. In contrast, using as ... as a shorthand for the previously proposed syntax cannot be extended naturally to support tuple/parameter pack expansion, and is inconsistent with the existing behavior of the as operator as described here.

The existing ... operator has been brought up a lot as a possible candidate for this operation due to its symmetry with the syntax for declaring varargs. I've done a little bit of prototyping and it seems like adding array/tuple/parameter pack splatting as an alternate use case could be implemented in a source compatible way. However, if it were adopted as a universal spread operator, it would preclude future Comparable conformances for Array, and possibly tuples due to conflicts with the range operator. Perhaps more importantly, as noted here by @xwu , we would also be introducing a second, distinct meaning for an existing operator, which should probably be avoided if possible.

That brings me to my current suggestion, the prefix *. This operator has a few nice characteristics:

  • It has no existing meaning in Swift today, so there are no source compatibility concerns. There'd be no need to worry about weird interactions with Comparable conformance down the road.
  • There's precedent for using it as a splat operator in languages like Python & Ruby
  • It's been brought up in the past as a potential tuple splat operator. Using it for arrays and other splat operations as well would be consistent with that
  • ... could keep its existing, single meaning
7 Likes

The main downside of prefix * is that we've been thinking about using it in various ways to make pointer-manipulation code easier. If we did, we probably would need to make it special in much the way postfix ! is.

I'm not saying that those ideas are a firm plan or that they would necessarily rule out using it for splat too—just that it's a concern.

1 Like

The multiplication operator has no existing meaning?

1 Like

I'm talking about prefix *, not infix *. There's plenty of existing precedent for infix and pre/postfix operators with different meanings in Swift (& for example).

Good to know! I included a brief mention in my in-progress proposal revisions referencing the comments from when SE-29 was accepted, but I didn't realize there was still ongoing work looking into this. It's certainly something we'll need to keep in mind.

Terms of Service

Privacy Policy

Cookie Policy