Search code examples
raku

Variable number of arguments to function/subroutine


I want to be able to run a function with a variable number of parameters in Raku, but after reading through https://docs.raku.org/language/functions#Arguments I don't see how it can be done.

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 Raku subroutine which will accept a variable number of strings?


Solution

  • TL;DR You're asking about variadic functions.1 Simple use is simple. Some Raku 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.

    Variadic variations

    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

    Constraining arguments

    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
    

    Positional vs named arguments; **@foo and *%foo

    Raku 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]
    

    Variadic positionals destructuring; +@foo and *@foo

    Raku 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

    Bonus section: 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).

    Footnotes

    1 The standard industry term for this is a variadic function. At the time of writing this answer, the Rakudo Raku compiler uses the industry standard term ("variadic") in error messages but the official Raku 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 Raku language and/or the Rakudo Raku 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!