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

Nik*_*nko 27 parameters perl shift iterable-unpacking

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

sub my_sub {
    my $self = shift;
    my $params = shift;
    ....
}
Run Code Online (Sandbox Code Playgroud)

然而,我的同事们很多人都在讲道,这shift实际上是邪恶的.你能解释我为什么要这样做吗?

sub my_sub {
    my ($self, $params) = @_;
    ....
}
Run Code Online (Sandbox Code Playgroud)

shift

Joh*_*usa 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  }, });
Run Code Online (Sandbox Code Playgroud)

在我的机器上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%      --
Run Code Online (Sandbox Code Playgroud)

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

shift在你想要移出调用者然后调用SUPER::方法然后传递剩余的@_原样的情况下,它也很有用.

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

但是,如果我my $foo = shift;在函数顶部进行了很长时间的操作,我可能会考虑使用大量复制@_.但在一般情况下,如果你有需要超过的参数了一把,使用命名参数的函数或方法(即,抓住所有@_%args散列或期待一个哈希引用参数)是一个更好的方法.

  • 您应该显示何时实际上更快到没有使用班次.我认为使用2或3个args复制@_会更快.根据经验,当有1个arg时我使用shift,当有超过1时复制@_. (6认同)

Cha*_*ens 20

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

sub new {
    my $class = shift;
    my %self = @_;
    return bless \%self, $class;
}
Run Code Online (Sandbox Code Playgroud)

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


dao*_*oad 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
}
Run Code Online (Sandbox Code Playgroud)

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

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

现在,如果您习惯性地使用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.

}
Run Code Online (Sandbox Code Playgroud)

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

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


cms*_*cms 8

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

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

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

例如

sub do_something {
 my ($foo) = @_;
}
Run Code Online (Sandbox Code Playgroud)

后来编辑到

sub do_something {
  my ($foo,$bar) = @_; # still works
}
Run Code Online (Sandbox Code Playgroud)

然而

sub do_another_thing {
  my $foo = shift;
}
Run Code Online (Sandbox Code Playgroud)

如果另一位同情地使用第一种形式的同事(也许他们认为移位是邪恶的)编辑你的文件并且心不在焉地更新这个以阅读

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

他们可能已经引入了一个微妙的bug.

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

例如

 my (%arguments) = (user=>'defaultuser',password=>'password',@_);
Run Code Online (Sandbox Code Playgroud)

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

  • 我不认为`我($ foo,$ bar)= shift;`是一个*微妙的*错误.这是头脑,不是吗? (2认同)
  • 我已经看过很多次了.当然是愚蠢的错误,但当你阅读很多内容时,很容易成为"代码盲". (2认同)