28

我经常shift用来解包函数参数:

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

然而,我的许多同事都在说教,shift实际上是邪恶的。你能解释一下为什么我更喜欢

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

shift

4

8 回答 8

51

使用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;但是,如果我在函数的顶部有很长的一系列操作,我可能会考虑使用批量复制 from@_来代替。但一般来说,如果您有一个函数或方法需要多个参数,则使用命名参数(即,@_%args哈希中捕获所有参数或期望单个哈希引用参数)是一种更好的方法。

于 2009-08-25T13:54:03.377 回答
21

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

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

shift当有一个论点或者我想以不同于其他论点的方式对待前几个论点时,我倾向于使用。

于 2009-08-25T13:59:34.557 回答
12

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

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

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.

}

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

如果我的同事说使用 shift 解包存在很大问题,我会要求演示它为什么不好。如果情况是可靠的,那么我会学到一些东西。如果这个案子是假的,我可以反驳它,帮助阻止反知识的传播。然后我建议我们确定一个标准方法并在以后的代码中遵循它。我什至可能会尝试建立一个 Perl::Critic 策略来检查确定的标准。

于 2009-08-25T16:15:32.007 回答
8

将@_ 分配给列表可以带来一些有用的附加功能。

它使以后在修改代码时添加额外的命名参数变得更容易一些人认为这是一个功能,类似于如何用尾随 '' 完成列表或散列,使得将成员附加到未来。

如果您习惯于使用这个习语,那么转移参数可能看起来有害,因为如果您编辑代码以添加额外的参数,如果您不注意,最终可能会出现一个微妙的错误。

例如

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

后来编辑为

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

然而

sub do_another_thing {
  my $foo = shift;
}

如果另一个同事教条地使用第一种形式(也许他们认为 shift 是邪恶的)编辑您的文件并心不在焉地更新它以阅读

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

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

当您一次分配少量参数时,分配给 @_ 可以更紧凑和更有效地使用垂直空间。如果您使用命名函数参数的哈希样式,它还允许您提供默认参数

例如

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

我仍然认为这是一个风格/品味的问题。我认为最重要的是一致地应用一种风格或另一种风格,遵循最不意外的原则。

于 2009-08-25T14:16:01.150 回答
1

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

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

 my $result = decode($theString);

 sub decode {
   my $string = shift;

   ...
 }

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

 sub encode {
   my ($string,$format) = @_;
   ...
 }
于 2009-08-25T14:20:05.480 回答
1

Perl::Critic 是你的朋友。它遵循 Damian Conway 的《Perl 最佳实践》一书中设置的“标准”。使用 --verbose 11 运行它可以解释为什么事情不好。不首先在您的潜艇中解包 @_ 是严重性 4(满分为 5)。例如:

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 `( @_ )'.
于 2009-08-25T16:30:45.687 回答
1

列表分配有优化。

我能找到的唯一参考是这个

5.10.0无意中禁用了优化,这导致列表分配的性能明显下降,例如通常用于从@_. 优化已恢复,性能回归已修复。

这是受影响的性能回归的一个示例。

sub example{
  my($arg1,$arg2,$arg3) = @_;
}
于 2009-08-25T17:48:12.317 回答
0

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

于 2009-08-25T13:53:09.400 回答