是“移位”吗?处理 Perl 子程序参数的邪恶?

发布于 2024-08-03 10:29:06 字数 309 浏览 9 评论 0原文

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

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

然而,我的许多同事都在宣扬 shift 实际上是邪恶的。您能解释一下为什么我应该选择

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

shift吗?

I'm frequently using shift to unpack function parameters:

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

However, many on my colleagues are preaching that shift is actually evil. Could you explain why I should prefer

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

to shift?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

别在捏我脸啦 2024-08-10 10:29:06

使用 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 版本上测试您的场景,以确保找到答案。

如果您想要移走调用者,然后调用 SUPER:: 方法,将剩余的 @_ 传递为-是。

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

但是,如果我在函数顶部有一系列很长的 my $foo = shift; 操作,我可能会考虑使用来自 @_ 的大量副本。但一般来说,如果您有一个函数或方法需要多个参数,请使用命名参数(即捕获 %args 哈希中的所有 @_或者期望单个散列引用参数)是更好的方法。

The use of shift to unpack arguments is not evil. It's a common convention and may be the fastest way to process arguments (depending on how many there are and how they're passed). Here's one example of a somewhat common scenario where that's the case: a simple accessor.

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  }, });

The results on perl 5.8.8 on my machine:

            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%      --

Not dramatic, but there it is. Always test your scenario on your version of perl on your target hardware to find out for sure.

shift is also useful in cases where you want to shift off the invocant and then call a SUPER:: method, passing the remaining @_ as-is.

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

If I had a very long series of my $foo = shift; operations at the top of a function, however, I might consider using a mass copy from @_ instead. But in general, if you have a function or method that takes more than a handful of arguments, using named parameters (i.e., catching all of @_ in a %args hash or expecting a single hash reference argument) is a much better approach.

满栀 2024-08-10 10:29:06

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

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

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

It is not evil, it is a taste sort of thing. You will often see the styles used together:

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

I tend to use shift when there is one argument or when I want to treat the first few arguments differently than the rest.

风渺 2024-08-10 10:29:06

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

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

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 解压参数,则可能会遇到一个愚蠢的大错误:

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

This is, as others have said, a matter of taste.

I generally prefer to shift my parameters into lexicals because it gives me a standard place to declare a group of variables that will be used in the subroutine. The extra verbosity gives me a nice place to hang comments. It also makes it easy to provide default values in a tidy fashion.

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
}

If you are really concerned about the speed of calling your routines, don't unpack your arguments at all--access them directly using @_. Be careful, those are references to the caller's data you are working with. This is a common idiom in POE. POE provides a bunch of constants that you use to get positional parameters by name:

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

Now the big stupid bug you can get if you habitually unpack params with shift is this one:

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.

}

I think that consistent code style is more important than any particular style. If all my coworkers used the list assignment style, I'd use it too.

If my coworkers said there was a big problem using shift to unpack, I'd ask for a demonstration of why it is bad. If the case is solid, then I'd learn something. If the case is bogus, I could then refute it and help stop the spread of anti-knowledge. Then I'd suggest that we determine a standard method and follow it for future code. I might even try to set up a Perl::Critic policy to check for the decided upon standard.

柠檬 2024-08-10 10:29:06

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

它使得稍后在修改代码时添加其他命名参数变得稍微容易一些。有些人认为这是一个功能,类似于用尾随“,”完成列表或散列使得在中追加成员稍微容易一些未来。

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

例如,

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
}

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

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

例如

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

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

Assigning @_ to a list can bring some helpul addtional features.

It makes it slightly easier to add additional named parameters at a later date, as you modify your code Some people consider this a feature, similar to how finishing a list or a hash with a trailing ',' makes it slightly easier to append members in the future.

If you're in the habit of using this idiom, then shifting the arguments might seem harmful, because if you edit the code to add an extra argument, you could end up with a subtle bug, if you don't pay attention.

e.g.

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

later edited to

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

however

sub do_another_thing {
  my $foo = shift;
}

If another colleague, who uses the first form dogmatically (perhaps they think shift is evil) edits your file and absent-mindedly updates this to read

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

and they may have introduced a subtle bug.

Assigning to @_ can be more compact and efficient with vertical space, when you have a small number of parameters to assign at once. It also allows for you to supply default arguments if you're using the hash style of named function parameters

e.g.

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

I would still consider it a question of style / taste. I think the most important thing is to apply one style or the other with consistency, obeying the principle of least surprise.

幽蝶幻影 2024-08-10 10:29:06

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

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

 my $result = decode($theString);

 sub decode {
   my $string = shift;

   ...
 }

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

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

I don't think shift is evil. The use of shift shows your willingness to actually name variables - instead of using $_[0].

Personally, I use shift when there's only one parameter to a function. If I have more than one parameter, I'll use the list context.

 my $result = decode($theString);

 sub decode {
   my $string = shift;

   ...
 }

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

 sub encode {
   my ($string,$format) = @_;
   ...
 }
桃气十足 2024-08-10 10:29:06

对列表分配进行了优化。

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

5.10.0 无意中禁用了优化,从而导致
列表中可测量的性能下降
赋值,例如经常用于
@_ 分配函数参数。
优化已恢复,
并修复了性能回归问题。

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

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

There is an optimization for list assignment.

The only reference I could find, is this one.

5.10.0 inadvertently disabled an optimization, which caused a
measurable performance drop in list
assignment, such as is often used to
assign function parameters from @_ .
The optimisation has been re-instated,
and the performance regression fixed.

This is an example of the affected performance regression.

sub example{
  my($arg1,$arg2,$arg3) = @_;
}
So要识趣 2024-08-10 10:29:06

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 `( @_ )'.

Perl::Critic is your friend here. It follows the "standards" set up in Damian Conway's book Perl Best Practices. Running it with --verbose 11 gives you an explanation on why things are bad. Not unpacking @_ first in your subs is a severity 4 (out of 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 `( @_ )'.
ˇ宁静的妩媚 2024-08-10 10:29:06

它本质上并不是邪恶的,但使用它来逐一完成子例程的参数相对较慢,并且需要更多的代码行。

It isn't intrinsically evil, but using it to pull off the arguments of a subroutine one by one is comparatively slow and requires a greater number of lines of code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文