Frage

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).

War es hilfreich?

Lösung 2

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.

Andere Tipps

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)

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;
        ...
    }
    

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( @_ );
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top