Variadic Parameters that Accept Array Inputs

But instead of casting it like [Any](values), it should probably be Array(values).

Very true! I am not a fan of that casting syntax so I avoid it most of the time. I don't know what I was thinking.

One last thing I think I forgot to mention, when actually using an input of Variadic<T>, you should be able to use it with all/most if the same things you can do with an Array for the most part, this would avoid excessive type conversions and make them much easier to work with (more versatile). Like you should have to type cast – Array(someVariadic) – a variable holding Variadic<T> when putting it in to a parameter of type Array<T>, but from some cases like this, they should for the most part be able to be passed around and what not like Arrays. You should also be able to do Variadic(someArray) (initialize a variadic collection with an Array, or any other Collection for that matter). But this would not negate with still having the option to do either splat(someArray) or someArray.splat.

Thoughts?

Variadic arguments are also a much nicer syntax for inputting multiple parameters.

Same comment re reading, the brackets are small enough to ‘disappear’ when reading.

I agree that if the Variadic<T> approach is taken it should pretty much work like Array<T> but almost certainly as a fixed size version.

I imagine having splat(someArray) or someArray.splat would be convenient but it doesn't necessarily add clarity over just using Variadic(someArray) for just a few more key strokes.

Why I say this is because you should be able to in essence, splat an Array or a Variadic into its individual elements.

Also, here is another example:
do you think Variadic<Int> should support an input of Variadic<Int>, Int, Int, Int or Array<Int>, Int, Int, Int; that is why you should be able to splat any Collection down into its individual elements.

If we have Variadic<Int> I think Variadic<Int>, Int, Int, Int would splat and Array<Int>, Int, Int, Int would create a a Variadic<Any> with 4 elements with the first being the Array<Int>.

Variadic Types


I am looking for everyone's opinions on this idea.


The creation of two variadic types:

  1. VariadicArray

    • The Type... syntax would refer to VariadicArray.
  2. VariadicSet

    • where as VariadicSet has to be implicitly referred to as VariadicSet<T>.

Both of these types can inherit from a protocol called Variadic which itself conforms to Collection.


Thoughts?

2 Likes

What is the purpose of VariadicSet? When would you use this over a plain Set?

2 Likes

Sets of fixed, and known-at-compile-time size. I can't think of a use-case atm, but IIRC math can be built from the ground up using only this type of element (fixed-size sets). So there's probably a use-case somewhere.

But more than variadic sets and arrays, I think it's more important to have types that are generic over values. That's more general and useful than variadic generics:

  • nArray<T, n : Int> as a variadic array of n elements of type T.
  • nSet<T, n : Int> as well.

And then you could also have types like these:

  • modNInt<n : Int>: an element over the set of integers modulo n. This would be great for type-safe modular arithmetic, used in cryptography for example.
3 Likes

How are variadics actually implemented? Like, how are the parameters passed? I think I’ve seen ContiguousArray in SIL for variadic calls—is that how they’re passed?

A trivial example: sil hidden @output.doSomething(x: Int...) -> () : $@convention(thin) (@owned Array<Int>) -> ()

Ah, that makes life easier.

Why not just allow you to say #variadic(someArray) in the parameter list? It avoids wacky redesigns, and I think it’s probably rare enough that a slightly awkward syntax is no big deal.

The thrust of my question is regarding variadics. There have been calls for FixedSizedArray that have nothing to do with variadics, and there are obvious uses and optimizations that can be done with them. If you want to introduce FixedSizeSet for similar reasons, that would make sense. However, I don't see why you'd tie them to variadics. The input to a function must maintain order of parameters, even when they are gathered into a single collection. This calls for an array, not a set.

3 Likes

Ahh, I see. That's a good point. Yeah, I don't see why you'd want variadic sets then. At least if what we call "variadics" is exclusively "variable input to functions".

I mean, for how basic the concept is, it feels strange to tie it directly to something like a Set, which asks for uniqueness semantics in the inputs. But I could very well be wrong. @Wildchild9, how did you envision this?


Edit: that said, the interesting thing (semantically) from a function that you know recieves a VariadicSet argument, is that it implies the following property:

  • Let f: VariadicSet<T> -> U be a function.
  • Then you know that f(a, b) = f(b, a) for all pairs of inputs a and b. We know that f is commutative, without knowing anything about its implementation.
  • The same is true for any length of input. The output will not change if you change the ordering of the parameters.

This is very interesting, but out of my field of expertise. Maybe @codafi and @harlanhaskins have opinions on this specific topic.

Maybe slightly off topic, but "Variadic parameter cannot have a default value" is sometimes clumsy.

I don't know that this needs to be encoded in the type system, though. The documentation for the function has to convey the same information, because what the method does strongly implies how it uses the inputs.

Trivial examples

func avg(_ input: Int...) -> Float {
   return Float(input.reduce(0, +)) / Float(input.count)
}

By known what the avg() function does, you can deduce that the order of inputs doesn't matter, but you expect duplicate elements to be preserved.

func max(_ input: Int...) -> Int {
    var i = input[0]; input.forEach { i = max(i, $0) }; return i
}

Again, you can tell that the max() function does not care about order, and duplicates are effectively ignored. Whether this is because the arguments are gathered into a set, or because max(i, i) == i is not really an important detail.

func join(_ strings: String...) -> String {
    return strings.reduce("", +)
}

Here, the order obviously does matter, and there's no expectation that duplicates are removed. How this is implemented isn't important.

That is true, but usually you'd like to test those properties just to make sure some change didn't screw up one of the functions in the codebase.

The neat thing about something like this being embedded in the type system is that you wouldn't need to test those properties. The compiler would verify them for you. That's why I find it so interesting.

That said, if you mean that from a documentation standpoint it's not necessary because you can document it with the comments and the function itself, then you're absolutely right.

What does everyone think about manual splatting versus automatic splatting?

I think that manual splatting is the best option as it provides more flexibility and will help eliminate confusion surrounding using collections in variadic parameters.

  • This could be implemented with a function version, splat(someVariadic), and an extension version, someVariadic.splat()
1 Like