Variadic parameters
A parameter written ...name collects every remaining positional argument into a List, and a collection is only unpacked into it when the call spreads it with the same three dots.
A variadic parameter, written ...name: Type, takes any number of positional arguments of Type and collects them
into a List<Type>. A caller who already holds a collection spreads it into the call with the same ....
Example#
fn sumAll(...numbers: Int): Int {
numbers.fold 0 { a, b => a + b }
}
const values = [1, 2, 3]
print sumAll(1, 2, 3)
print sumAll(...values)Syntax#
fn <name>(..., ...<name>: <Type>): <ReturnType> { ... }
<call>(..., ...<expression>)Rules#
...name: Typeinside a parameter list declares a variadic parameter, and its body seesnameas aList<Type>. It is written last, and every positional argument from its position onward fills it.A call spreads an
Iterate<Type>into a variadic parameter with...expression. Spreading works with any type that implements theIteratetrait, not onlyList, so aSetspreads the same way aListdoes.fn sumAll(...numbers: Int): Int { numbers.fold 0 { a, b => a + b } } const someSet = Set.of 4, 5, 6 print sumAll(1, ...someSet)...only fills a variadic parameter: spreading into an ordinary one is rejected, naming the parameter it was aimed at.Does not compilefn configure(port: Int) { print port } const settings = [8080] configure(...settings) // error: `port` is not a variadic parameter, so `...` cannot spread into itA variadic parameter that receives no argument becomes an empty list, not a missing-argument error.
fn sumAll(...numbers: Int): Int { numbers.fold 0 { a, b => a + b } } print sumAll()A parameter declared after a variadic one can only be filled by label. Every positional argument from the variadic parameter's position onward fills it, so a later parameter is never reached positionally.
Does not compilefn describe(...numbers: Int, label: String): String { "{label}: {numbers}" } print describe(1, 2, "totals") // error: `describe` has no argument for `label` // error: Expected `Int64`, found `String`
What this is not#
A collection is not unpacked by standing in a positional argument on its own. Only ... unpacks it; without it,
the whole collection becomes the one element the variadic parameter collects.
fn sumAll(...numbers: Int): Int {
numbers.fold 0 { a, b => a + b }
}
const values = [1, 2, 3]
print sumAll(...values)fn wrapped(...numbers: List<Int>): List<List<Int>> {
numbers
}
const values = [1, 2, 3]
print wrapped(values)The second program is not an error - it type checks, because values is one argument of type List<Int> and the
parameter's element type is List<Int> too. Its result is [[1, 2, 3]], a list of one list, which is rarely what was
meant. Spreading (...values) is what turns the collection's own elements into the arguments.
...rest is not *args from a language with no other way to pass many arguments. Every parameter before it is
still ordinary and still labelled the same way, and only the first variadic parameter of a signature can ever collect
anything positionally (see rule 4).
Related#
- Arguments and labels - how a positional argument is matched to a parameter.
- Declaring a function - the rest of the parameter list.