是';班次';处理Perl子例程参数的邪恶?

是';班次';处理Perl子例程参数的邪恶?,perl,parameters,shift,iterable-unpacking,Perl,Parameters,Shift,Iterable Unpacking,我经常使用shift解压函数参数: sub my_sub { my $self = shift; my $params = shift; .... } 然而,我的许多同事都在鼓吹说,shift实际上是邪恶的。你能解释一下我为什么更喜欢你吗 sub my_sub { my ($self, $params) = @_; .... } shift?从本质上讲并不邪恶,但使用它逐个提取子例程的参数相对较慢,需要更多的代码行。使用shift解压参数并不邪恶。这是

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

sub my_sub {
    my $self = shift;
    my $params = shift;
    ....
}
然而,我的许多同事都在鼓吹说,
shift
实际上是邪恶的。你能解释一下我为什么更喜欢你吗

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

shift

从本质上讲并不邪恶,但使用它逐个提取子例程的参数相对较慢,需要更多的代码行。

使用
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系列在函数顶部的操作,但是,我可能会考虑使用<代码> > @ < /COD>的大量复制。但一般来说,如果您有一个函数或方法接受的参数多于一小部分,那么使用命名参数(即在
%args
散列中捕获所有
@
,或者期望一个散列引用参数)是一种更好的方法。

这不是坏事,而是一种尝试。您经常会看到一起使用的样式:

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

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

为列表指定@可以带来一些有用的附加功能

在以后的日期中添加额外的命名参数稍微容易一些,当你修改代码时,有些人认为这是一个特性,类似于如何完成列表或用尾随“哈希”,这使得在将来追加成员稍微容易一些。 如果您有使用此习惯用法的习惯,那么改变参数可能看起来是有害的,因为如果您编辑代码以添加额外的参数,如果您不注意,最终可能会出现一个微妙的错误

e、 g

后来编辑为

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
}
他们可能引入了一个微妙的错误

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

e、 g


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

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

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

 my $result = decode($theString);

 sub decode {
   my $string = shift;

   ...
 }

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

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

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

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

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提供了一组常量,可用于按名称获取位置参数:

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策略来检查已确定的标准。

Perl::Critic是您的朋友。它遵循Damian Conway的书Perl最佳实践中建立的“标准”。使用--verbose11运行它可以解释为什么情况不好。如果你的潜水艇没有先打开包装,那将是4级(5级中的4级)。例如:


列表分配有一个优化

我能找到的唯一参考资料是

5.10.0
无意中禁用了优化,导致 可测量的绩效下拉列表 作业,如经常用来 从
@
分配功能参数。 已重新恢复优化, 性能回归是固定的

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

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

-1查找错误信息。正如@johnsiracusa所说,使用shift来处理争论是一种常见的Perl习惯用法。我看不出他在哪里反驳这不是一种常见的Perl习惯用法。他确实说速度较慢(这是真的,因为参数数组必须被操纵,而不是仅仅被访问)。根据我的基准测试,对于2个或更少的参数,换档速度更快。列表分配在3个或更多时间更快。因为任何需要3、4或1的东西
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.

}
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 `( @_ )'.
sub example{
  my($arg1,$arg2,$arg3) = @_;
}