为了处理Perl子程序参数,'转移'是邪恶的吗?

时间:2009-08-25 13:50:58

标签: perl parameters shift iterable-unpacking

我经常使用shift来解压缩函数参数:

sub my_sub {
    my $self = shift;
    my $params = shift;
    ....
}

然而,我的同事们很多人都在宣扬shift实际上是邪恶的。你能解释为什么我更喜欢

sub my_sub {
    my ($self, $params) = @_;
    ....
}

shift

8 个答案:

答案 0 :(得分:50)

使用shift解包参数并不是邪恶的。这是一种常见的约定,可能是处理参数的最快方式(取决于它们的数量和传递方式)。以下是一个常见情况的例子:情况就是这样:一个简单的访问者。

use Benchmark qw(cmpthese);

sub Foo::x_shift { shift->{'a'} }
sub Foo::x_ref   { $_[0]->{'a'} }
sub Foo::x_copy  { my $s = $_[0]; $s->{'a'} }

our $o = bless {a => 123}, 'Foo';

cmpthese(-2, { x_shift => sub { $o->x_shift },
               x_ref   => sub { $o->x_ref   },
               x_copy  => sub { $o->x_copy  }, });

我机器上perl 5.8.8的结果:

            Rate  x_copy   x_ref x_shift
x_copy  772761/s      --    -12%    -19%
x_ref   877709/s     14%      --     -8%
x_shift 949792/s     23%      8%      --

不是戏剧性的,但确实如此。始终在目标硬件上的perl版本上测试您的场景,以确定无误。

shift在您想要移出调用者然后调用SUPER::方法,并按原样传递剩余的@_时也很有用。

sub my_method
{
  my $self = shift;
  ...
  return $self->SUPER::my_method(@_);
}

但是,如果我在函数顶部进行了一系列非常长的my $foo = shift;操作,我可能会考虑使用@_中的大量复制。但一般来说,如果你有一个函数或方法需要多个参数,使用命名参数(即,在@_哈希中捕获所有%args或期望单个哈希引用参数)是一种更好的方法。

答案 1 :(得分:20)

这不是邪恶,它是一种品味的东西。您经常会看到一起使用的样式:

sub new {
    my $class = shift;
    my %self = @_;
    return bless \%self, $class;
}

当有一个参数或我想以不同于其他参数的方式处理前几个参数时,我倾向于使用shift

答案 2 :(得分:11)

正如其他人所说,这是一种品味问题。

我通常更喜欢将我的参数转换为词汇,因为它为我提供了一个标准的位置来声明将在子例程中使用的一组变量。额外的详细程度为我提供了一个悬挂评论的好地方。它还可以轻松地以整齐的方式提供默认值。

sub foo {
    my $foo = shift;       # a thing of some sort.
    my $bar = shift;       # a horse of a different color.
    my $baz = shift || 23; # a pale horse.

    # blah
}

如果您真的担心调用例程的速度,请不要解压缩您的参数 - 使用@_直接访问它们。请注意,这些是对您正在使用的呼叫者数据的引用。这是POE中常见的习语。 POE提供了一组常量,用于按名称获取位置参数:

sub some_poe_state_handler {
    $_[HEAP]{some_data} = 'chicken';
    $_[KERNEL]->yield('next_state');
}

现在,如果你习惯性地使用shift解包params,那么你可以得到的一个大愚蠢的错误是:

sub foo {
    my $foo = shift;
    my $bar = shift;
    my @baz = shift;

    # I should really stop coding and go to bed.  I am making dumb errors.

}

我认为一致的代码风格比任何特定风格都重要。如果我的所有同事都使用了列表分配方式,我也会使用它。

如果我的同事说使用轮班解包有一个大问题,我会要求证明它为什么不好。如果案件是坚实的,那么我会学到一些东西。如果案件是假的,我可以反驳它,并帮助阻止反知识的传播。然后我建议我们确定一个标准方法,并将其用于未来的代码。我甚至可能尝试建立一个Perl :: Critic策略来检查决定标准。

答案 3 :(得分:8)

将@_分配给列表可以带来一些帮助功能。

稍微更容易在日后添加其他命名参数,因为您修改代码有些人认为这是一个功能,类似于完成列表或带尾随','的哈希,使得它更容易在将来追加成员。

如果你习惯使用这个习惯用法,那么移动参数可能看起来很有害,因为如果你编辑代码来添加额外的参数,你可能会得到一个微妙的错误,如果你不支付注意。

e.g。

sub do_something {
 my ($foo) = @_;
}

后来编辑为

sub do_something {
  my ($foo,$bar) = @_; # still works
}

<强>然而

sub do_another_thing {
  my $foo = shift;
}

如果另一位以教条方式使用第一种形式的同事(也许他们认为转变是邪恶的)会编辑您的文件并且心不在焉地更新此内容以阅读

sub do_another_thing {
  my ($foo, $bar)  = shift; # still 'works', but $bar not defined
}

他们可能已经引入了一个微妙的错误。

当您只需要分配少量参数时,使用垂直空间分配给@_可以更紧凑和高效。如果您使用命名函数参数的散列样式

,它还允许您提供默认参数

e.g。

 my (%arguments) = (user=>'defaultuser',password=>'password',@_);

我仍然认为这是一个风格/品味的问题。我认为最重要的是将一种风格或另一种风格应用于一致性,遵循最少惊喜的原则。

答案 4 :(得分:1)

我认为shift不是邪恶的。使用shift表示您愿意实际命名变量 - 而不是使用$_[0]

就个人而言,当函数只有一个参数时,我会使用shift。如果我有多个参数,我将使用列表上下文。

 my $result = decode($theString);

 sub decode {
   my $string = shift;

   ...
 }

 my $otherResult = encode($otherString, $format);

 sub encode {
   my ($string,$format) = @_;
   ...
 }

答案 5 :(得分:1)

Perl ::评论家是你的朋友。它遵循Damian Conway的Perl Best Practices一书中的“标准”。使用--verbose 11运行它可以解释为什么事情不好。不首先在你的潜艇中拆包@_是严重性4(满分5分)。 E.g:

echo 'package foo; use warnings; use strict; sub quux { my foo= shift; my (bar,baz) = @_;};1;' | perlcritic -4 --verbose 11

Always unpack @_ first at line 1, near 'sub quux { my foo= shift; my (bar,baz) = @_;}'.
  Subroutines::RequireArgUnpacking (Severity: 4)
    Subroutines that use `@_' directly instead of unpacking the arguments to
    local variables first have two major problems. First, they are very hard
    to read. If you're going to refer to your variables by number instead of
    by name, you may as well be writing assembler code! Second, `@_'
    contains aliases to the original variables! If you modify the contents
    of a `@_' entry, then you are modifying the variable outside of your
    subroutine. For example:

       sub print_local_var_plus_one {
           my ($var) = @_;
           print ++$var;
       }
       sub print_var_plus_one {
           print ++$_[0];
       }

       my $x = 2;
       print_local_var_plus_one($x); # prints "3", $x is still 2
       print_var_plus_one($x);       # prints "3", $x is now 3 !
       print $x;                     # prints "3"

    This is spooky action-at-a-distance and is very hard to debug if it's
    not intentional and well-documented (like `chop' or `chomp').

    An exception is made for the usual delegation idiom
    `$object->SUPER::something( @_ )'. Only `SUPER::' and `NEXT::' are
    recognized (though this is configurable) and the argument list for the
    delegate must consist only of `( @_ )'.

答案 6 :(得分:1)

列表分配有一个优化。

我能找到的唯一参考是this one

  

5.10.0无意中禁用了优化,导致了一个   列表中可衡量的性能下降   任务,如经常习惯   从@_分配函数参数。   优化已经恢复,   并且性能回归已经确定。

这是受影响的绩效回归的一个例子。

sub example{
  my($arg1,$arg2,$arg3) = @_;
}

答案 7 :(得分:0)

它本质上不是邪恶的,但是使用它逐个拉出子程序的参数相对较慢并且需要更多行代码。