6
sub foo {
 $arg1 = shift @_;
 $arg2 = shift @_;
 # ...
}

What's the benefit of this idiom? I see only disadvantages compared to explicitly working with $_[0], $_[1], ... The array has to be shifted, which is time consuming. It is destroyed, so that at a later point in time, the arguments have vanished (sad if you need them again and have overwritten your $arg1 with a different value).

ubuplex
  • 189
  • 1
  • 5
  • One biggie mentioned below is that a change in `@_` changes your parameters, a side effect you don't want. Plus, shifting documents what parameters you expect. You could use `my ( $param1, $param2 ) = @_;` but I prefer `shift` and I usually do `my $var = shift;` and implicitly use `@_` rather than explicitly use it in `my $var = shift @_;`. – David W. Jan 30 '14 at 16:20
  • @David W., `shift` doesn't have the same "and nothing else" implication that `my (...) = @_;` has. – ikegami Jan 30 '14 at 17:32
  • @ikegami - I also end up destroying `@_` which means I can't get back the original parameters if I messed up. I do it this way mainly for _beauty_. It makes it easy to see what the parameters are, and you can add a comment to the end to explain each parameter. – David W. Jan 30 '14 at 18:14
  • Yeah, shift definitely wins for comments and defaults. – ikegami Jan 30 '14 at 18:47

4 Answers4

9

Shifting @_ is common in OO perl so parameters can be separated from instance of the class which is automatically added as first element of @_.

It can also be used to write less when assigning default values for input parameters, although personally I don't find it appealing,

sub foo {
  my $arg1 = shift // 2;
  my $arg2 = shift // 7;
  # ...
}

I see only disadvantages compared to explicitly working with $[0], $1, ...

Instead of working with $_[0], $_[1], assigning whole @_ array at once is better/less error prone/more readable practice.

my ($arg1, $arg2) = @_;

Also note that @_ elements are aliased to passed variables, so accidental changes could happen,

sub foo {
  $_[0] = 33;
  $_[1] = 44;
}

my ($x, $y) = (11,22);

print "($x, $y)\n";
foo($x, $y);
print "($x, $y)\n";

output

(11, 22)
(33, 44)
mpapec
  • 50,217
  • 8
  • 67
  • 127
5

It's faster to access $_[0], etc directly than to use named parameters[1], but there are a number of good reasons to prefer named parameters.

  1. By far the most important reason, using named parameters serves as a form of documentation.

  2. Using @_ directly is hard to read because it's hard to keep track of which argument has what value.

    For example, "What does $_[4] contain? Is it the rows? Or is that $_[5]?"

  3. It's easy to use the wrong index by accident.

    • It's easy to type the wrong number.
    • It's easy to think you want one number when you want another.
    • It's easy to overlook an index when changing a sub's parameters.

  4. Using @_ directly is hard to read because of the amount of symbols.

    Compare

    grep { $_[0]{$_} =~ /.../ } keys %{$_[0]}
    

    with

    grep { $foos->{$_} =~ /.../ } keys %$foos
    
  5. It's useful for providing defaults.

    sub f {
       my $x = shift // "abc";
       my $y = shift // "def";
       ...
    }
    
  6. Copying into a scalar effectively introduces pass-by-copy semantics, which can be useful.

    $ perl -le'my $x=123; sub f {                  $x=456; print $_[0]; } f($x);'
    456
    
    $ perl -le'my $x=123; sub f { my $arg = shift; $x=456; print $arg;  } f($x);'
    123
    

Notes:

  1. Whether it's my preferred

    sub f {
       my (...) = @_;
       ...
    }
    

    or

    sub f {
        my ... = shift; 
        my ... = shift;
        ...
    }
    
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • I agree with all points. But the basic question remains: Why should I shift instead of doing a list assignment or a sequence of scalar assignments? – ubuplex Jan 30 '14 at 17:24
  • 2
    @ubuplex, Your question asked for the benefit of this idiom **compared to explicitly working with `$_[0]`, `$_[1]`**. That's a completely different question, and the comments is not a place to ask it. – ikegami Jan 30 '14 at 17:34
  • You are right. My question was poorly formulated. What I should have asked instead is "Does shifting in argument passing not introduce an unnecessary time overhead over using `@_` directly?" (Be it by using it directly, by a list assignment, or by a sequence of assignments." Now that so many people have answered, I do not want to ask this question again. – ubuplex Jan 31 '14 at 14:33
  • I'm not sure how that's any different. I would simply have phrased my answer as follows: "Yes, it adds time overhead, but here are 6 reasons why it isn't unnecessary." – ikegami Jan 31 '14 at 14:37
5

I agree with all points. But the basic question remains: Why should I shift instead of doing a list assignment or a sequence of scalar assignments?

Since I do shifting, I'll explain why I do it.

There are three may ways you can handle parameters to a subroutine:

Method 1: List Assignment

 sub foo {
    my ( $user, $date, $system ) = @_;   # No shifting, and @_ preserved

Method 2: Individual Assignment from @_

 sub foo {
    my $user   = $_[1];  
    my $date   = $_[2];   # Must be YYYY-MM-DD
    my $system = $_[3];   # Optional

Method 3: Using "shift"

sub foo {
    my $user    = shift;
    my $date    = shift;    # Must be YYYY-MM-DD
    my $system  = shift;    # Optional

Method 1 is popular. Ikegami uses it, and so do many other advanced Perl developers. It gives you a parameter list and gives you a way of saying these are my parameters and no others.

However, Method 2 and Method 3 give you a nice, easy to read parameter list, and you can add comments to the end of the line. However, Method 2 also has the advantage of maintaining the value of @_, so why use shift?

Take a look at Method 2 again. See a problem? I started with @_[1] and not with @_[0] -- a common error. Plus, as you're developing your subroutine, you might decide to reorder your parameters. Using Method 2 means renumbering them. Method 3 doesn't require renumbering, so you don't end up with this:

 sub foo {
    my $date   = $_[1];   # Must be YYYY-MM-DD
    my $user   = $_[0];  
    my $system = $_[2];   # Optional

Um, what's the order of the parameters again?

So, what about preserving the value of @_? If you write a program where you change the value of a parameter, you're probably aren't going to be restoring it from @_. And, if you do, you are likely to produce some error prone code. If you need to munge a parameter, put it in another variable and munge that. This isn't 1975 where computers had only 4 kilobytes of memory.

And, talking about the 1970s, computers are fast enough that the amount of time of a few (dozen, hundred, thousands) shift operations isn't going to make that much difference in your total runtime. If your program is inefficient, optimize it where it is inefficient, and not by possibly shaving a few milliseconds eliminating a shift. More time will be spent maintaining your program than actually running it.


Damian Conway (The author of Perl Best Practices) recommends both Method #1 and Method #3. He states that Method #1 is "...is more concise, and it keeps the parameters together in a horizontal list, which enhances readability, provided that the number of parameters is small." (the emphasis is mine).

Damian states about Method #3: "The shift-based version is preferable, though, whenever one or more arguments has to be sanity-checked or needs to be documented with a trailing comment."

I simply use Method #3 all of the time. This way, I don't have to worry about reformatting if my parameter list grows, and I simply think it looks better.

David W.
  • 105,218
  • 39
  • 216
  • 337
  • 1
    Rearranging the paramaters means reordering the lines in Method 3. Concerning optimizations: I've just made an experiment. I've computed the 42-th Fibonacci number (fib(0)=fib(1)=1, fib(n)=fib(n-1)+fib(n-2)). This ends up in endless recursive calls, all of which only do parameter passing, 2 comparisons, 3 arithm. ops., so the overhead of parameter passing is maximized. The running times in secs for `$_[0]/$n=$_[0]/($n)=@_/$n=shift`: 244/246/244/245. So you are right. I accept this as the answer. (Of course, the iterative version takes 0.00 sec.) – ubuplex Feb 01 '14 at 10:59
1

When writing methods in object classes, I always shift the invocant off @_ first, but then just unpack the remaining arguments out, thus preserving all the other non-invocant arguments. This allows the call sites to look more like the method definition itself, in terms of what's inside the parens.

sub method
{
  my $self = shift;
  my ( $x, $y, $z ) = @_;
}

$obj->method( "x", "y", "z" );

This way if I have to delegate the method call elsewhere, I can simply pass @_ itself:

sub do_thing_with_whatsit
{
  my $self = shift;
  $self->{whatsit}->do_thing( @_ );
}
LeoNerd
  • 8,344
  • 1
  • 29
  • 36