18

In my code, I'm been using the fairly primitive method of extraction parameters from a function call as follows:

sub addSix ($$$$$$) {
    my ($a, $b, $c, $d, $e, $f) = (shift, shift, shift, shift, shift, shift);
    return $a + $b + $c + $d + $e + $f;
}

print addSix (1, 2, 3, 4, 5, 6) . "\n";

(forget the primitive code, the salient bit is the multiple shift calls).

Now that seems rather messy to me and I though Perl may have something like:

my ($a, $b, $c, $d, $e, $f) = shift (6);

or something similar.

But I cannot find anything like that. I know I can use arrays for this but I think I'd still have to unpack the array into individual scalars. That wouldn't be too bad for the example case above, where the six parameters are similar, but I'm more interested in the case where they're not really suitable as an array.

How can you extract parameters without ending up with a morass of shift keywords?

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Note : Avoid using `$a` and `$b` as variable names as they are intended to be used inside `sort` blocks only. – Zaid Jan 12 '12 at 07:43
  • @Zaid, that's _sample_ code, I actually name my real variables a little less succinctly than that :-) – paxdiablo Jan 12 '12 at 09:28
  • 2
    obviously not for an add example, but for a real subroutine that takes more than a handful of arguments, it might be best to start using named parameters: `my %params = @_;` and call it as `mysub foo => 1, bar => 2, baz => 3, ...` – Eric Strom Jan 12 '12 at 16:37
  • @brian d foy link appears dead. Is this the same article: http://www.effectiveperlprogramming.com/2011/10/understand-why-you-probably-dont-need-prototypes/? – James Kingsbery Oct 18 '13 at 15:17

3 Answers3

38

You can simply type:

my ($a, $b, $c, $d, $e, $f) = (@_);

If you didn't have that prototype, and if that sub got called with more than six arguments, the ones after the sixth are simply "not matched", $f would be set to the sixth argument.

If you want to catch all the arguments after the sixth, you can do it like this.

my ($a, $b, $c, $d, $e, $f, @others) = (@_);

If your list of scalars is longer than the list on the right side, the last elements will be undef.

Mat
  • 202,337
  • 40
  • 393
  • 406
  • D'Oh. In the case of accidentally passing ten parameters, will `$f` be the sixth, or a combination of six through ten somehow? Or will the `$$$$$$` prevent passing ten? ... Never mind, your "in the first four minutes" edit clarified that for me. – paxdiablo Jan 12 '12 at 07:07
  • 1
    The prototype prevents passing anything else than six arguments. If you don't have the argument list prototype (so you can pass anything), `$f` will still be the sixth argument, the rest of the argument list is simply not "matched". – Mat Jan 12 '12 at 07:10
  • 9
    There is no need for the `()` around `@_` – Zaid Jan 12 '12 at 07:42
  • 4
    @Zaid: I find the symmetry of having the `()` on both sides of the `=` more aesthetically pleasing. – Mat Jan 12 '12 at 08:47
  • 1
    You can call it with any number of arguments if you prefix it with `&` `&addSix(1,2)` `&addSix(1..10)`. – Brad Gilbert Jan 12 '12 at 16:06
7

The use of prototypes is highly discouraged unless there is a real need for it.

As always with Perl, there is more than one way to do it.

Here's one way to guarantee adding only the first six parameters that are passed:

use List::Util 'sum';

sub addSix { sum @_[0..5] }

Or if you like self-documenting code:

sub addSix {

    my @firstSix = @_[0..5];  # Copy first six elements of @_
    return sum @firstSix;
}
Zaid
  • 36,680
  • 16
  • 86
  • 155
  • 1
    Why are prototype discouraged? I would have thought they were invaluable in detecting cases where you _want_ a fixed number of params. – paxdiablo Jan 12 '12 at 09:27
  • 1
    @paxdiablo : I'll refer you to [some light reading](http://stackoverflow.com/q/297034/133939). And [then some](http://stackoverflow.com/q/3991143/133939). – Zaid Jan 12 '12 at 09:52
  • 2
    @paxdiablo : [`"Prototypes are not for argument validation"`](http://stackoverflow.com/a/3991173/133939) – Zaid Jan 12 '12 at 09:53
  • Interesting looking into that, I'll have to check it out. However, just like "Code Complete", a lot of it sems to be _guidelines_ - see http://www.perlmonks.org/?node_id=655882 for example with a comment from one Randal L Schwartz himself :-) – paxdiablo Jan 12 '12 at 11:56
  • Not that I'm dissing your answer, it's quite handy and, now that I'm doing Perl stuff again after _many_ years, I can probably get work to spring for a copy of PBP. – paxdiablo Jan 12 '12 at 12:03
  • 1
    [Understand why you probably don’t need prototypes](http://www.effectiveperlprogramming.com/blog/1406) – brian d foy Jan 12 '12 at 14:32
4

I realise this is an old thread, but it got me thinking about a better way to shift multiple values. This is all just a bit of fun... Mainly posting this for educational purposes.

Sure, ($x, $y) = @_ is great if you want to retain @_, but perhaps you want to shift your arguments for some reason? Perhaps you want any additional subroutine functionality determined by the number of remaining arguments in @_.

The cleanest one-line way I could think of to do this is with a simple map

sub shiftySub {
    map { $_ = shift } my ($v, $w, $x, $y);
    # @_ now has up to 4 items removed
    if (@_) { ... } # do stuff if arguments remain
}
  • If 4 arguments are provided, @_ is now empty in the sub scope.
  • If 5 arguments are provided, @_ has 1 item remaining in the sub scope.
  • If 3 arguments are provided, @_ is empty, and $y is undef in the sub scope.

Regarding paxdiablo's theoretical shift(6) operator, we could create our own function that performs this operation...

sub shifter (\@;$) {
    my ( $array, $n ) = ( @_, 1 );
    splice( @$array, 0, $n );
}

The function works by enforcing a pass-by-ref prototype (one of the very limited reasons you should ever use prototypes) to ensure the array gets shifted in the calling scope. You then use it simply like this...

my @items = ('one', 'two', 'three', 'four');
my ($x, $y) = shifter(@items, 2);
# or as a replacement for shift
my $z = shifter(@items)
# @items has 1 item remaining in this scope!

Of course, you could also use this shifter function inside your other subs. The main downside to a function like this is you must keep track of the number of assignments on both sides of the operator.

I hope my $post = 'informative' || 'interesting';

Community
  • 1
  • 1
elcaro
  • 2,227
  • 13
  • 15