Does an anonymous parameter in a Perl 6 signature discard the value?

203 views Asked by At

In the Perl 6 Signature docs, there's an example of an anonymous slurpy parameter:

sub one-arg (@)  { }
sub slurpy  (*@) { }
one-arg (5, 6, 7); # ok, same as one-arg((5, 6, 7))
slurpy  (5, 6, 7); # ok
slurpy   5, 6, 7 ; # ok

There are no statements in the subroutine, mostly because the text around this is about the parameter list satisfying the signature rather than what the subroutine does with it.

I was playing with that and trying to make a subroutine that takes a list of one of more items (so, not zero items). I didn't particularly care to name them. I figured I'd still have access to the argument list in @_ even with the signature. However, you get @_ when you don't have a signature:

$ perl6
To exit type 'exit' or '^D'
> sub slurpy(*@) { say @_ }
===SORRY!=== Error while compiling:
Placeholder variable '@_' cannot override existing signature
------> sub⏏ slurpy(*@) { say @_ }

Is there another way to get the argument list, or does the anonymous parameter discard them? I see them used in the section on type constraints, but there isn't an example that uses any of the parameter values. Can I still get the argument list?

3

There are 3 answers

5
moritz On BEST ANSWER

The values aren't discarded; you can for example access it through nextsame:

multi access-anon(*@) is default {
    say "in first candidate";
    nextsame;
}
multi access-anon(*@a) {
    @a;
}
say access-anon('foo')

Output:

in first candidate
[foo]

But to get to your original objective (an array with at least one element), you don't actually need to access the list; you can use a sub-signature:

sub at-least-one(@ [$, *@]) { }
at-least-one([1]);   # no error
at-least-one([]);    # Too few positionals passed; expected at least 1 argument but got only 0 in sub-signature
3
Brad Gilbert On

To have @_ populated, the signature either has to be implicit or explicit, not both.

sub implicit             {      say @_ } # most like Perl 5's behaviour
sub explicit     ( *@_ ) {      say @_ }
sub placeholder          { $^a; say @_ }

This applies to blocks as well

my &implicit    =        {      say @_ }
my &explicit    = -> *@_ {      say @_ }
my &placeholder =        { $^a, say @_ }

Blocks can also have an implicit parameter of $_, but @_ takes precedence if it is there.

{     say $_ }(5) # 5
$_ = 4;
{ @_; say $_ }(5) # 4

It makes sense to do it this way because one programmer may think it works the way you think it does, or that it is slurpy like it would be if implicit, and another may think it gets all of the remaining arguments.

sub identical (     @_           ) { say @_ }
sub slurpy    (    *@_ ( @, *@ ) ) { say @_ } # same as implicit
sub slurpy2   (   **@_ ( @, *@ ) ) { say @_ } # non-flattening
sub remaining ( @, *@_           ) { say @_ }

identical  [1,2];       # [1 2]
slurpy    $[1,2],3,4,5; # [[1 2] 3 4 5]
slurpy2    [1,2],3,4,5; # [[1 2] 3 4 5]
remaining  [1,2],3,4,5; # [3 4 5]

@_ may also be added as a mistake, and in that case it would be preferable for it to produce an error.


There is no way to get at the raw arguments without declaring a capture parameter.

sub raw-one    ( |capture ( @ ) ) { capture.perl }
sub raw-slurpy ( |capture, *@   ) { capture.perl }

raw-one    [1,2]; # \([1, 2])
raw-slurpy  1,2 ; # \(1, 2)
2
smls On

Does an anonymous parameter in a Perl 6 signature discard the value?

Yes, unless you capture an argument using some named parameter in the function's signature, it won't be available in the function's body. (PS: They're not literally discarded though, as moritz's answer shows.)

I figured I'd still have access to the argument list in @_ even with the signature.

@_ is not an alternative to using parameters - it is a parameter.

Every function has a well-defined signature that specifies its parameters, and they represent the only mechanism for the function body to get at the values the function is passed.

It's just that there are three different ways to declare a function signature:

  1. If you explicitly write out a signature, that's what is used.
  2. If you use placeholder parameters (@_ or $^foo etc.) in the body of the function, the compiler uses that information to build the signature for you:
    say { $^a + @_ }.signature;  # ($a, *@_)
  3. If neither of the above is the case, then the signature becomes:
    • In case of subroutines, one that accepts zero arguments:
      say (sub { 42 }).signature; # ()
    • In case of bare blocks, one that accepts zero or one argument available as $_:
      say { 42 }.signature;  # (;; $_? is raw)

In all cases, the function ends up with an unambiguous signature at compile time. (Trying to use $^foo in a function that already has an explicit signature, is a compile-time error.)

Is there another way to get the argument list

Make sure to capture it with a non-anonymouys parameter. If you want it to be accessible as @_, then call it that in the explicit signature you're writing.

I was [...] trying to make a subroutine that takes a list of one of more items

You can use a sub-signature for that:

sub foo (*@_ [$, *@]) { ... };

Or alternatively a where constraint:

sub foo (*@_ where .so) { ... };