I want to be able to run a function with a variable number of parameters in Perl6, but after reading through https://docs.perl6.org/language/functions#Arguments I don't see how it can be done. I see numerous links for other languages, and a warning that "Questions with similar titles have frequently been downvoted" but I don't see this anywhere in the documentation or StackOverflow.
I want to do something like this:
some-test($v1, $v2)
or
some-test($v3)
but not have a separate function using multi
for each
How can I construct a Perl6 subroutine which will accept a variable number of strings?
TL;DR You're asking about variadic functions.1 Simple use is simple. Some P6 features, most notably positional and named arguments, and variadic destructuring, add some wrinkles. Also, see the other answers which are very helpful too.
variable number of arguments
Simple use of a simple variadic function:
sub variadic (|args) { say args .elems }
variadic(); # 0
variadic('1'); # 1
variadic('1', '2'); # 2
A |foo
parameter slurps up all remaining arguments into a Capture
bound to sigilless identifier foo
:
sub variadic ($a, @b, %c, |others) { say others[0] }
variadic 1, [2,3], (:foo), 4, [5,6], (:bar); # 4
In the above example the others
parameter "slurps"1 up the last three listed arguments starting with the 4
.
Things aren't always simple:
variadic(1, 2); # 2 -- works but args are Ints
variadic(:foo,:bar); # 0 -- where did :foo, :bar go?
variadic((:foo)); # 1 -- works; arg is Pair (:foo)
variadic((1, 2)); # 1 -- works; arg is List (1,2)
In the rest of this answer I explain:
Constraining arguments, eg ensuring they're all strings.
Positional vs named arguments; **@foo
and *%foo
Variadic positionals destructuring; +@foo
and *@foo
variable number of strings
You can impose any constraints you want on slurped arguments by using a where
clause. (Which you can in turn package into a subset
if you want.)
For example, to constrain all the arguments to be of type Str
:
sub variadic (|args where .all ~~ Str) { say args .elems }
variadic(); # 0
variadic('1'); # 1
variadic('1', '2'); # 2
variadic(1); # Constraint type check failed
**@foo
and *%foo
P6 supports both positional and named arguments.
Using |foo
in a signature always captures all remaining arguments, both positional and named:
sub slurps-into-WHAT (|args) { say WHAT args }
slurps-into-WHAT(); # (Capture)
A Capture
stores positional arguments in an internal list accessible via positional subscripting (i.e. args[...]
) and named arguments in a hash accessible via associative subscripting (i.e. args<...>
or args{...}
):
sub variadic (|args) { say " {args[1]} {args<named2>}" }
variadic('pos0', 'pos1', named1 => 42, named2 => 99); # pos1 99
Sometimes it's preferable to collect just named args, or just positional ones, or to collect both but in separate parameters.
To collect named args, use a parameter of the form *%foo
(one asterisk prefix and a hash arg):
sub variadic-for-nameds (*%nameds) { say %nameds }
variadic-for-nameds(:foo, :bar); # {bar => True, foo => True}
(Note that all methods collect named args in this way even if their signature doesn't explicitly say so.2)
To collect positional args, use a parameter of the form **@foo
(two asterisk prefix immediately followed by an array arg):
sub variadic-for-positionals (**@positionals) { say @positionals }
variadic-for-positionals(1, 2, 3); # [1 2 3]
+@foo
and *@foo
P6 provides a range of non-variadic argument destructuring features.
The first variadic positionals destructuring parameter form is +@foo
. This has exactly the same effect as **@foo
except in one case; if the variadic parameter gets just a single argument, and that argument is a list or array, then the parameter is bound to the content of that list or array, stripping away the list/array container:
sub variadic-plus (+@positionals) { say @positionals }
variadic-plus(1,2,3); # says same as for **@positionals
variadic-plus(1); # says same as for **@positionals
variadic-plus([1]); # [1] -- instead of [[1]]
variadic-plus((1,2,3)); # [1 2 3] -- instead of [(1 2 3)]
The +@foo
form was introduced to support the "single arg rule". It's used by core devs writing built ins. Users may wish to use it when they want the same behavior.
The other variadic positionals destructuring form is *@foo
. It does the same thing as +@foo
in that it extracts the content from list or array container args and throws the container away. But it's much more aggressive:
It does this for all arguments.
If an argument is a list rather than an array ((...)
rather than [...]
) then it descends into that list and recursively repeats the exercise if an element of the list is itself another inner list or array.
Thus:
sub variadic-star (*@positionals) { say @positionals }
variadic-star((1,2),[3,4]); # [1 2 3 4]
variadic-star((1,2),(3,4,(5,6,(7,8)))); # [1 2 3 4 5 6 7 8]
variadic-star((1,2),(3,4,[5,6,(7,8)])); # [1 2 3 4 5 6 (7 8)]
(Note how it stripped the container from the [5,6,(7,8)]
array but did not descend into it.)
One final thing; are there variadic named destructuring parameters? You tell me.3
foo(...)
vs foo (...)
(I included this bonus section in the hope it heads off confusion. If this section is itself confusing, just ignore it.)
Routine calls can be written with or without parentheses around their list of arguments and they mean the same thing. The opening parenthesis must follow the routine name immediately, without intervening whitespace:
sub foo (|args) { say args[0] }
foo 'a', 'b'; # a
foo('a', 'b'); # a
(This rule only applies to the routine call, between the routine name and its arguments. It does not apply to the routine declaration, between the routine name and its parameters. For the latter, the declaration, you can leave no space or use space as I have above with sub foo (|args)
and it makes no difference.)
If you insert whitespace between the foo
and the opening parenthesis in a call you're writing something different:
foo ('a', 'b'); # (a b)
That calls foo
with one argument, the one list ('a', 'b')
in contrast to foo('a', 'b')
which calls foo
with two arguments, the two values inside the parentheses.
The following calls foo
with two arguments, both of which are lists:
foo ('a', 'b', 'c'), ('d', 'e', 'f'); # (a b c)
You may nest parentheses:
foo( ('a', 'b', 'c'), ('d', 'e', 'f') ) ; # (a b c)
foo (('a', 'b', 'c'), ('d', 'e', 'f')) ; # ((a b c) (d e f))
foo( (('a', 'b', 'c'), ('d', 'e', 'f')) ) ; # ((a b c) (d e f))
The latter two foo
calls get one argument, the one list ( ('a', 'b', 'c'), ('d', 'e', 'f') )
(that happens to contain two inner lists).
1 The standard industry term for this is a variadic function. At the time of writing this answer, the Rakudo P6 compiler uses the industry standard term ("variadic") in error messages but the official P6 doc tends to use the word "slurpy" instead of "variadic" and talks of "slurping up arguments".
2 Methods always have an implicit variadic named parameter called %_
if one is not explicitly specified:
say .signature given my method foo {} # (Mu: *%_)
3 The P6 language and/or the Rakudo P6 compiler currently allows parameters to be written in the form +%foo
and **%foo
. It doesn't really make sense to have variadic nameds destructuring. Perhaps that explains why both these forms do insane things:
**%foo
appears to be indistinguishable from %foo
.
+%foo
appears to be indistinguishable from **@foo
except for using the identifier %foo
instead of @foo
. The object bound to %foo
is an Array
!
If you just want to be able to take 1 or 2 values then the easiest way is to mark the second value as optional :
sub some-test( $v1, $v2? ) { ... }
Or you can define a default value :
sub some-test( $v1, $v2="default" ) { ... }
Or if you want any number of values (1 or more) you can use a slurpy with a where clause :
sub some-test( *@v where *.elems > 0 ) { ... }
You can use signature destructuring
sub some-test(*@all [$first, *@rest]) { ... } # must have 1 or more parameters
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With