array splatting for variadic parameters

What happened with this proposal? This looks easy to implement.

func sumOf(numbers: Int...) -> Int {
      ...
  }
typealias Function = [Int] -> Int
let sumOfArray = unsafeBitCast(sumOf, Function.self)
sumOfArray([1, 2, 3])

···

Hello everyone.

I understand that topic has already been discussed in the past, but I failed to get any information on its current state of affairs.

I guess the subject of this mail is explicit, but to make sure I’m clear, here's a small snippet that illustrates the problem:

func f(args: Int…) {
  // Some implementation ...
}
// Now it's impossible to call f without explicitly naming its parameters.

For many use-cases, this problem can be solved by overloading f so that it explicitly accepts an array.

func f(_ args: [Int]) {
  // Some implementation ...
}

func f(_ args: Int…) {
  f(args)
}

Some people also advocate (myself generally included) that one should prefer the signature explicitly marking args as an array, as the syntactic overhead of wrapping the arguments with “” when calling f is arguably bearable. However, in some other situations, this approach might not be applicable. For instance, one may simply not be able to modify the original function. Another use-case may be a function that should forward its own variadic parameters.

In a variety of other languages, there exists a way to do this. For instance, Python can “unpack” (or splat) a list into function arguments by prefixing it with *:

def f(*args):
  # Some implementation …

f(*[1, 2, 3]) # == f(1, 2, 3)

I was wondering if such feature could be supported by Swift, and if not, why.

Syntactically, I like the use of “…”, which would mirror function signatures:

f(…[1, 2, 3]) // == f(1, 2, 3)

Thanks.

--
Dimitri Racordon

There has been a solution to the same problem that’s imho much nicer, because instead of adding fundamental new syntax, it removes a piece of C-legacy:

Basically,instead of
func f(args: Int…)
you would just declare
func f(args: @variadic [Int])
or even
func f(args: [Int])
and interpret any argument that’s a comma-separated list as an array — or, and that’s imho another useful aspect, something else that can be expressed with an array literal
func f(args: Set<Int>)

So you would not only make the language surface smaller, but also add new abilities that basically come for free (they would still have to be implemented, though ;-)

···

Am 30.11.2017 um 22:13 schrieb Cao, Jiannan via swift-evolution <swift-evolution@swift.org>:

What happened with this proposal? This looks easy to implement.

func sumOf(numbers: Int...) -> Int {
      ...
  }
typealias Function = [Int] -> Int
let sumOfArray = unsafeBitCast(sumOf, Function.self)
sumOfArray([1, 2, 3])

Hello everyone.

I understand that topic has already been discussed in the past, but I failed to get any information on its current state of affairs.

I guess the subject of this mail is explicit, but to make sure I’m clear, here's a small snippet that illustrates the problem:

func f(args: Int…) {
  // Some implementation ...
}
// Now it's impossible to call f without explicitly naming its parameters.

For many use-cases, this problem can be solved by overloading f so that it explicitly accepts an array.

func f(_ args: [Int]) {
  // Some implementation ...
}

func f(_ args: Int…) {
  f(args)
}

Some people also advocate (myself generally included) that one should prefer the signature explicitly marking args as an array, as the syntactic overhead of wrapping the arguments with “” when calling f is arguably bearable. However, in some other situations, this approach might not be applicable. For instance, one may simply not be able to modify the original function. Another use-case may be a function that should forward its own variadic parameters.

In a variety of other languages, there exists a way to do this. For instance, Python can “unpack” (or splat) a list into function arguments by prefixing it with *:

def f(*args):
  # Some implementation …

f(*[1, 2, 3]) # == f(1, 2, 3)

I was wondering if such feature could be supported by Swift, and if not, why.

Syntactically, I like the use of “…”, which would mirror function signatures:

f(…[1, 2, 3]) // == f(1, 2, 3)

Thanks.

--
Dimitri Racordon

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

What about calling a framework variadic function that I could not re-declare?
like print

···

在 2017年12月2日,上午1:26,Tino Heth <2th@gmx.de <mailto:2th@gmx.de>> 写道:

There has been a solution to the same problem that’s imho much nicer, because instead of adding fundamental new syntax, it removes a piece of C-legacy:

Basically,instead of
func f(args: Int…)
you would just declare
func f(args: @variadic [Int])
or even
func f(args: [Int])
and interpret any argument that’s a comma-separated list as an array — or, and that’s imho another useful aspect, something else that can be expressed with an array literal
func f(args: Set<Int>)

So you would not only make the language surface smaller, but also add new abilities that basically come for free (they would still have to be implemented, though ;-)

Am 30.11.2017 um 22:13 schrieb Cao, Jiannan via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

What happened with this proposal? This looks easy to implement.

func sumOf(numbers: Int...) -> Int {
      ...
  }
typealias Function = [Int] -> Int
let sumOfArray = unsafeBitCast(sumOf, Function.self)
sumOfArray([1, 2, 3])

Hello everyone.

I understand that topic has already been discussed in the past, but I failed to get any information on its current state of affairs.

I guess the subject of this mail is explicit, but to make sure I’m clear, here's a small snippet that illustrates the problem:

func f(args: Int…) {
  // Some implementation ...
}
// Now it's impossible to call f without explicitly naming its parameters.

For many use-cases, this problem can be solved by overloading f so that it explicitly accepts an array.

func f(_ args: [Int]) {
  // Some implementation ...
}

func f(_ args: Int…) {
  f(args)
}

Some people also advocate (myself generally included) that one should prefer the signature explicitly marking args as an array, as the syntactic overhead of wrapping the arguments with “” when calling f is arguably bearable. However, in some other situations, this approach might not be applicable. For instance, one may simply not be able to modify the original function. Another use-case may be a function that should forward its own variadic parameters.

In a variety of other languages, there exists a way to do this. For instance, Python can “unpack” (or splat) a list into function arguments by prefixing it with *:

def f(*args):
  # Some implementation …

f(*[1, 2, 3]) # == f(1, 2, 3)

I was wondering if such feature could be supported by Swift, and if not, why.

Syntactically, I like the use of “…”, which would mirror function signatures:

f(…[1, 2, 3]) // == f(1, 2, 3)

Thanks.

--
Dimitri Racordon

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

What about calling a framework variadic function that I could not re-declare?
like print

That’s a good question — in „regular“ frameworks, variadic functions as we have them now wouldn’t exist anymore, but C is a different story…
My expectation (without deep knowledge of the compatibility-layer) is that it isn't hard to solve this; after all, it’s basically just a small change in syntax (although afair, T… is a special case in the type system).

@Tino I was nor aware of an SE proposal for that. Can you please link a reference?

The lack of splatting for equivalent measures is noticeable. Instantiating a C: Collection, ExpressibleByArrayLiteral with arbitrarily many elements is impossible -- which is just silly.

1 Like

Sadly, there has been no formal proposal yet - probably, there was to much stress before the Swift 3 release :frowning:
But there are a lot of links:

Apparently, variadics are even less powerful than I thought ;-)

I see. I read "there has been a solution to the same problem" to mean "this has been solved", while you meant "in previous discussions, other solutions have been proposed".

FWIW, it doesn't seem to me (after skimming those links) that attributes have more fans the splatting.

Splatting seems to be more common, and probably it's harder to think outside the box...
Just because other languages chose splatting, it's not necessarily better - and to me, it just feels nuts:

  1. You splat an array with some kind of new syntax
  2. You call a function that says it doesn't take an array
  3. The function that you are calling receives a plain boring array
  4. If that function has to forward to another function, you return to step 1.

The alternative of using nothing but an array, which can be forwarded to any other function that works with arrays is just so much simpler (and as you are not limited to arrays, you could also use variadic syntax for sets and other types without conversion).

The function that you are calling receives a plain boring array

What else would you use? It seems the ideal choice for a finite sequence of values of the same type with a statically known length.

Let's take a step back: what's really going on here? To my understanding:

  • "Variadic parameters" are nothing more than syntactic sugar for passing anarray of static size (by _definition_¹).
  • If the compiler encounters a list of parameters and matches it to a variadic declaration, it wraps that list in an array initialization.

Now, "splatting" is nothing but a small indicator for the compiler to match an array of fitting type with a variadic parameter and just forward it (instead of failing, or -- in case of a generic method -- wrapping that array in another array).
I want to stress that there's no conversion back and forth. Variadics are arrays.

Why would we want this? For me, the core problem is that if we have to functions with the same signature, we expect to be able to delegate from one to the other with (close to) zero overhead, syntactic or runtime. Without splatting, this is impossible to do and we end up creating methods with variadic parameters only as forwarders to non-variadic ones -- nullifying the brevity the sugar gave us.


¹ Of course, you could use lists, or any other data structure, as long as you agree to a single one (or add more syntax to disambiguate). Some languages even use variadic maps to implement named parameters. There are trade-offs here, as always.

It is possible by completely removing variadics (which have their own, exotic type), which would also make splatting obsolete. Instead, we would have some real syntactic sugar that allows to write parameters that are expressible by an array literal without the [].
At the call side, everything would stay as it is now, and at the declaration side, you would just write the array variant (which you have to write anyways).

override func sum(_ numbers: [Int] {
   print("Will add \(numbers)")
   super.sum(numbers)
}

func showVariants() {
  sum([1, 2, 3]) // no sugar
  sum(1, 2, 3) // sugar
}

Todays variadics are more than syntactic sugar. All those conversations are probably optimized away, but the model is unnecessary complex - and splatting would add even more complexity.

I suspect you haven't thought this through (because if it was that simple, why isn't it in the language?) but I'll play along because you may have a point.

A general rule could run along the lines of:

If a parameter

  1. does not have a label,
  2. conforms to ExpressibleByArrayLiteral with ArrayLiteralElement = A,
  3. is not followed by a label-free parameter of a type that conforms to ExpressibleByArrayLiteral with ArrayLiteralElement = B where B is comparable¹ to A, and
  4. is not followed by a label-free parameter of a type comparable with A,

allow to drop the brackets.

While that does sound doable, it's already quite complex -- and I don't touch on generic methods at all, and I'm sure to have forgotten things beyond that. Are you really sure more magic is easier/better than a little sugar?

That said, if variadics are implemented in Swift as more than a sprinkle of sugar already (I wouldn't know either way), then that may well be the root of the problem.

Would maybe an attribute be a Swifty way to go about this? Similar to @autoclosure, in a way.

Either way, I stand by my earlier statement that adding a splatting "operator" (which really is just a compiler hint, conceptually) is probably the simplest solution for the real problem (which is not how nice a language Swift is, but that variadics are not "idempotent".)


  1. I'm thinking of comparable as A : B or B : A. But that is probably too short-sighted: if A and B have any common subtypes (except for Nothing), two "variadic" lists can not always be split unambiguously.
1 Like

It seems like maybe such a rule could piggyback on whatever rule allows trailing closures to work. If-and-only-if an array is the last parameter in the function, you could skip the brackets? Or if we imagined it being consistent with trailing closures, it might even look like this:

sum [1, 2, 3]

Just tossing that out there. :stuck_out_tongue:

That would be a simpler version, yes. I was provocatively going for maximum power; for calling a method like like foo(_ s: Set<Int>, a: [String]) it is, in principle, possible to treat both parameters as variadic. The corner cases, though...

Dropping parentheses is going too far, I think, since Swift doesn't even allow to drop them if there are no parameters. Trailing closures are the only exception, and that's as well.