Enhanced Variadic Parameters

(Adrian Zubarev) #42

What does it print today (not at my mac for today anymore)? Otherwise I'd say it prints 3, 1, 3.

(Davide De Franceschi) #43

I think it's reasonable that the automatic variadic conversion would work only when necessary and the actual signature has precedence. So in this case it would be

func foo(_ x: variadic [Any]) { print(x.count) }

// It matches the type of the argument, so no conversion necessary
foo([0, 1, 2] as [Any]) // 3
// It doesn't match, but the variadic conversion makes it match, so it gets applied and the argument is actually `[[0, 1, 2]]`
foo([0, 1, 2] as Any) // 1
// It matches, no conversion necessary
foo([0, 1, 2]) // 3
1 Like
(Matthew Johnson) #44

The arguments in the variadic parameter list must have the correct type argument’s ArrayLiteralElement or you will get a compiler error. In the sample in question ArrayLiteralElement == Int. [1, 3, 4] and [8, 9] at of type [Int] not type Int.

(Matthew Johnson) #45

Ignoring the fact that this signature makes no sense for the example function, if the declaration did look like this then the code would work and T == [Int].

The interesting case in this generic example is if the user passes a single array of type [Int]. Does the compiler treat that as a direct array parameter and infer T == Int or does it treat this as a single element in a variadic list of arrays, inferring T == [Int] with an argument of type [[Int]]. I think the former is what we want, but this would be an edge case people would need to be aware of.

(Matthew Johnson) #46

This would not change under this pitch. The proposal does not introduce any kind of splatting at all.

(Matthew Johnson) #47

This is what I would expect as well. When there is a single argument of the variadic parameter type (or a subtype of it) that argument is passed directly. It would not be coerced to Any here.


The discussion is about a single declaration that can be called either variadically or by passing in a collection. Whether you call that “splatting” or not is immaterial, the problem regarding [Any] remains.

Moreover, just two posts above where I gave the example, @DevAndArtist had written “I think we could upgrade T... to the same functionality”. Thus I was pointing out an edge-case to consider.

As I said in my previous post, they are all currently treated as passing a single argument to the variadic call, hence it prints “1, 1, 1”. That is to say, in every case I showed, the compiler treats the single argument as the first of a potentially longer variadic list, and thus wraps it in an array whose count is the number of variadic arguments passed, hence 1.

That is the existing behavior for passing an [Any] instance to a variadic Any....

(Slava Pestov) #49

I think the best way to address this need is to add a new syntax for passing an array in lieu of a variadic parameter at the call site. Something like:

func myVarargs(_: Int...) {}

myVarargs(1, 2, 3, 4) // ordinary call
let arr = [1, 2, 3, 4]
myVarargs(...arr) // splatted call
(Matthew Johnson) #50

Sorry if I misunderstood part of your post. Is this the behavior you think makes the most sense? This proposal is all about improving variadics so we can define variadic to adopt different behavior if that makes sense. I think it does but I'm very much open to hearing other opinions.

(Matthew Johnson) #51

I would really prefer to avoid call site splat syntax if at all possible. It would be nice to be able to add variadic to an existing declaration without breaking source (and possibly even without breaking ABI).


I’m not weighing in with an opinion yet, just mentioning something that ought to be considered with respect to ambiguity and source-compatibility.

(Slava Pestov) #53

Other than mangling, the ABI for a variadic function is the same as one taking an array.

(Matthew Johnson) #54

Awesome. Will it be possible for the new variadic parameters to mangle the same as a non-variadic parameter?

(Slava Pestov) #55

Depends on what you're trying to do. If two declarations mangle the same, they cannot be overloaded.

(Matthew Johnson) #56

A central part of the purpose of this pitch is to avoid the need for overloading altogether.

They would still mangle differently as a similar declaration in a different module wouldn't they (i.e. @DevAndArtist's use case for retroactively adding variadics to a library that doesn't declare a parameter as variadic would still work).

1 Like
(Slava Pestov) #57

Yes, Swift symbol mangling always includes the module the symbol is defined in. For members of extensions, this is the module containing the extension.

(Matthew Johnson) #58

That’s what I thought, thanks for confirming. IMO, this means the call-site splat syntax would be a bad idea because it would make adding variadic an ABI-breaking change. I don’t think we need it.

Where both the declared type and a single element variadic list would be valid code the compiler should prefer the declared type as being more specific. I suspect this is what people would expect in the vast majority of cases. If the programmer really intends a single element collection it is trivial to add brackets at the call site. If the compiler preferred the single element collection there would not necessarily be a way for the call site to get it to accept the collection itself.

(Jens Ayton) #59

I think this edge case adds unnecessary complexity and confusion. I would strongly prefer explicit splat syntax over being able to use collections and variadic argument lists interchangeably.

(Xiaodi Wu) #60

I very much like the overall idea of expanding what variadic parameters can do. Adding the ability to label variadic arguments, or to have them passed in as a type other than Array, are valuable on their own. I have no doubt they would gain wide support (but for the inevitable bikeshedding of syntax).

These goals you name above also have merit, but I would urge you to split these into separate discussions and ideas.

The ability to label variadic arguments is orthogonal to the question of how the compiler treats overloads that differ only as to variadic and non-variadic array parameters. As noted above and as you know, Swift already has rules about this. And as at least one reader expressed above, they were excited about the overall idea and entirely missed the part where you were proposing to change the rules here too.

Sure, you can argue that you propose only to have these rule changes apply to the newly proposed syntax and not the existing one, so nothing is being changed—technically. But having two variadics features with differing overload resolution rules is not a tenable endpoint for the language, and the obvious eventuality is that the old rules will be displaced by the new rules. That is a separate issue from the overall idea and should stand or fall on its own merits. I would strongly object to using a pitch with a popular overall idea as a vehicle for other changes (or, to be precise, setting up a situation in which such a change becomes highly likely or inevitable in the future).

With respect to tuple splatting, again, your rationale has merit. However, the plan of record as articulated by the core team in removing implicit tuple splatting was that some future syntax would be introduced for explicit tuple splatting. While you may disagree with that direction, I think it’s safe to say that the design for additional features should work with, not against, the core team’s intended direction. If indeed you wish to change that direction, then such an intention should be discussed separately and either accepted or rejected as the new plan of record on its own merits.

(Matthew Johnson) #61

Personally, I think this proposal is scoped reasonably. It really isn’t my choice to make though. Moving this forward will rely on an implementer. Of course the core team and review managers also have a role to play. If any one of these parties decides that we should tackle a subset of the pitch in an initial proposal that is what will happen.

Are you talking specifically about the issue described by @Nevin? The semantics of the 3 / 1 / 3 behavior here is not separable from this basic proposal. The only time it is possible to introduce new semantics for variadics without breaking source is in the initial proposal.

The semantics of avoiding the need to write forwarding overloads is clearly very popular. The only question is whether collections can be passed directly or whether some kind of splatting syntax is necessary. I am opposed to requiring splatting as I believe making a non-variadic API variadic should be a source compatible change.

Tuple splatting is much different than variadics. I have no intention whatsoever of challenging this direction for tuple splatting. There is a loose relationship between the two but I don’t think a plan of record regarding tuple splatting determines what can be posed with regard to variadics.

In the proposed design, variadics are effectively syntactic sugar that allows you to omit collection literal brackets when calling a function. The primary API of the function remains that it accepts an argument of a collection type. This carries the implication that a call which directly passes a collection is more specific than a call which requires implicit collection brackets.

Of course the core team is free to make the determination that they prefer to go in a different direction. This isn’t up to you or I. I am certainly not going to refrain from proposing what I think is a good design just because in your opinion it is contradictory to an intended direction of the core team. The core team is perfectly capable of speaking for themselves on this topic if they feel it is necessary. If they choose to do so I would of course acknowledge and respect their preferred direction.