Perl 的子例程参数的移位与 @_ 的赋值之间有区别吗?

发布于 2024-07-10 11:43:43 字数 596 浏览 12 评论 0原文

让我们暂时忽略 Damian Conway 的最佳实践,即对于任何给定的子例程,位置参数不超过三个。

下面的两个示例在性能或功能方面有什么区别吗?

使用 shift

sub do_something_fantastical {
    my $foo   = shift;
    my $bar   = shift;
    my $baz   = shift;
    my $qux   = shift;
    my $quux  = shift;
    my $corge = shift;
}

使用 @_

sub do_something_fantastical {
    my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
}

假设两个示例在性能和功能方面相同,那么人们对一种格式与另一种格式的看法如何? 显然,使用 @_ 的示例代码行数较少,但是使用 shift 不是更清晰吗?如另一个示例所示? 欢迎有充分理由的意见。

Let us ignore for a moment Damian Conway's best practice of no more than three positional parameters for any given subroutine.

Is there any difference between the two examples below in regards to performance or functionality?

Using shift:

sub do_something_fantastical {
    my $foo   = shift;
    my $bar   = shift;
    my $baz   = shift;
    my $qux   = shift;
    my $quux  = shift;
    my $corge = shift;
}

Using @_:

sub do_something_fantastical {
    my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
}

Provided that both examples are the same in terms of performance and functionality, what do people think about one format over the other? Obviously the example using @_ is fewer lines of code, but isn't it more legible to use shift as shown in the other example? Opinions with good reasoning are welcome.

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

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

发布评论

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

评论(9

鸩远一方 2024-07-17 11:43:43

存在功能上的差异。 shift 修改 @_,并且来自 @_ 没有。 如果您之后不需要使用 @_,那么这种差异对您来说可能并不重要。 我尝试始终使用列表分配,但有时会使用 shift

但是,如果我从 shift 开始,就像这样:

 my( $param ) = shift;

我经常创建这个错误:

 my( $param, $other_param ) = shift;

那是因为我不经常使用 shift,所以我忘记了分配的右侧将其更改为 @_。 这就是不使用 shift 的最佳实践的要点。 我可以像您在示例中所做的那样为每个 shift 制作单独的行,但这很乏味。

There's a functional difference. The shift modifies @_, and the assignment from @_ does not. If you don't need to use @_ afterward, that difference probably doesn't matter to you. I try to always use the list assignment, but I sometimes use shift.

However, if I start off with shift, like so:

 my( $param ) = shift;

I often create this bug:

 my( $param, $other_param ) = shift;

That's because I don't use shift that often, so I forget to get over to the right hand side of the assignment to change that to @_. That's the point of the best practice in not using shift. I could make separate lines for each shift as you did in your example, but that's just tedious.

陌路黄昏 2024-07-17 11:43:43

至少在我的系统上,它似乎取决于 Perl 的版本和体系结构:

#!/usr/bin/perl -w
use strict;
use warnings;
use autodie;

use Benchmark qw( cmpthese );

print "Using Perl $] under $^O\n\n";

cmpthese(
    -1,
    {
        shifted   => 'call( \&shifted )',
        list_copy => 'call( \&list_copy )',
    }
);

sub call {
    $_[0]->(1..6);  # Call our sub with six dummy args.
}

sub shifted {
    my $foo   = shift;
    my $bar   = shift;
    my $baz   = shift;
    my $qux   = shift;
    my $quux  = shift;
    my $corge = shift;

    return;
}

sub list_copy {
    my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
    return;
}

结果:

Using Perl 5.008008 under cygwin

              Rate   shifted list_copy
shifted   492062/s        --      -10%
list_copy 547589/s       11%        --


Using Perl 5.010000 under MSWin32

              Rate list_copy   shifted
list_copy 416767/s        --       -5%
shifted   436906/s        5%        --


Using Perl 5.008008 under MSWin32

              Rate   shifted list_copy
shifted   456435/s        --      -19%
list_copy 563106/s       23%        --

Using Perl 5.008008 under linux

              Rate   shifted list_copy
shifted   330830/s        --      -17%
list_copy 398222/s       20%        --

所以看起来 list_copy 通常比移位快 20%,除了在 Perl 5.10 下,移位实际上稍快一些!

请注意,这些都是很快得出的结果。 实际速度差异将大于此处列出的速度,因为 Benchmark 还会计算调用和返回子例程所需的时间,这将对结果产生调节作用。 我还没有进行任何调查来了解 Perl 是否正在进行任何特殊类型的优化。 你的旅费可能会改变。

保罗

At least on my systems, it seems to depend upon the version of Perl and architecture:

#!/usr/bin/perl -w
use strict;
use warnings;
use autodie;

use Benchmark qw( cmpthese );

print "Using Perl $] under $^O\n\n";

cmpthese(
    -1,
    {
        shifted   => 'call( \&shifted )',
        list_copy => 'call( \&list_copy )',
    }
);

sub call {
    $_[0]->(1..6);  # Call our sub with six dummy args.
}

sub shifted {
    my $foo   = shift;
    my $bar   = shift;
    my $baz   = shift;
    my $qux   = shift;
    my $quux  = shift;
    my $corge = shift;

    return;
}

sub list_copy {
    my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
    return;
}

Results:

Using Perl 5.008008 under cygwin

              Rate   shifted list_copy
shifted   492062/s        --      -10%
list_copy 547589/s       11%        --


Using Perl 5.010000 under MSWin32

              Rate list_copy   shifted
list_copy 416767/s        --       -5%
shifted   436906/s        5%        --


Using Perl 5.008008 under MSWin32

              Rate   shifted list_copy
shifted   456435/s        --      -19%
list_copy 563106/s       23%        --

Using Perl 5.008008 under linux

              Rate   shifted list_copy
shifted   330830/s        --      -17%
list_copy 398222/s       20%        --

So it looks like list_copy is usually 20% faster than shifting, except under Perl 5.10, where shifting is actually slightly faster!

Note that these were quickly derived results. Actual speed differences will be bigger than what's listed here, since Benchmark also counts the time taken to call and return the subroutines, which will have a moderating effect on the results. I haven't done any investigation to see if Perl is doing any special sort of optimisation. Your mileage may vary.

Paul

错々过的事 2024-07-17 11:43:43

我认为移位示例比使用 @_ 慢,因为它有 6 个函数调用而不是 1 个。它是否明显甚至可测量是另一个问题。 将每个项目放入 10k 次迭代的循环中并计时。

至于美观,我更喜欢@_方法。 使用移位方法和意外剪切和粘贴似乎很容易弄乱变量的顺序。 另外,我见过很多人做这样的事情:

sub do_something {
   my $foo = shift;
   $foo .= ".1";

   my $baz = shift;
   $baz .= ".bak";

   my $bar = shift;
   $bar .= ".a";
}

恕我直言,这非常令人讨厌,很容易导致错误,例如,如果您剪切 baz 块并将其粘贴到 bar 块下。 我完全赞成在使用变量的地方定义变量,但我认为在函数顶部定义传入的变量优先。

I would imagine the shift example is slower than using @_ because it's 6 function calls instead of 1. Whether or not it's noticeable or even measurable is a different question. Throw each in a loop of 10k iterations and time them.

As for aesthetics, I prefer the @_ method. It seems like it would be too easy to mess up the order of the variables using the shift method with an accidental cut and paste. Also, I've seen many people do something like this:

sub do_something {
   my $foo = shift;
   $foo .= ".1";

   my $baz = shift;
   $baz .= ".bak";

   my $bar = shift;
   $bar .= ".a";
}

This, IMHO, is very nasty and could easily lead to errors, e.g. if you cut the baz block and paste it under the bar block. I'm all for defining variables near where they're used, but I think defining the passed in variables at the top of the function takes precedence.

请远离我 2024-07-17 11:43:43

通常我使用第一个版本。 这是因为我通常需要将错误检查与班次一起进行,这样更容易编写。 说,

sub do_something_fantastical {
    my $foo   = shift || die("must have foo");
    my $bar   = shift || 0;  # $bar is optional
    # ...
}

Usually I use the first version. This is because I usually need to have error checking together with the shifts, which is easier to write. Say,

sub do_something_fantastical {
    my $foo   = shift || die("must have foo");
    my $bar   = shift || 0;  # $bar is optional
    # ...
}
若相惜即相离 2024-07-17 11:43:43

我更喜欢将 @_ 解压为列表(您的第二个示例)。 不过,就像 Perl 中的所有内容一样,在某些情况下使用移位可能很有用。 例如,旨在在基类中重写的 passthru 方法,但您希望确保它们在不被重写的情况下仍然有效。


package My::Base;
use Moose;
sub override_me { shift; return @_; }

I prefer to unpack @_ as a list (your second example). Though, like everything in Perl, there are instances where using shift can be useful. For example passthru methods that are intended to be overridden in a base class but you want to make sure that things still work if they are not overridden.


package My::Base;
use Moose;
sub override_me { shift; return @_; }

零時差 2024-07-17 11:43:43

我更喜欢使用

sub do_something_fantastical {
    my ( $foo, $bar, $baz, $qux, $quux, $corge ) = @_;
}

因为它更具可读性。 当这段代码不经常被调用时,它是值得的。 在极少数情况下,您希望经常调用函数而不是直接使用@_。 它仅对非常短的函数有效,并且您必须确保该函数将来不会演变(Write Once 函数)。 在这种情况下,我在 5.8.8 中进行了基准测试,对于单个参数,移位比 $_[0] 更快,但对于使用 $_[0] 和 $_[1] 的两个参数,移位比移位更快。

sub fast1 { shift->call(@_) }

sub fast2 { $_[0]->call("a", $_[1]) }

但回到你的问题。 我也更喜欢在一行中进行 @_ 赋值,而不是通过这种方式对许多参数进行移位,

sub do_something_fantastical2 {
    my ( $foo, $bar, $baz, @rest ) = @_;
    ...
}

当怀疑 @rest 不会太大时。 在其他情况下

sub raise {
    my $inc = shift;
    map {$_ + $inc} @_;
}

sub moreSpecial {
    my ($inc, $power) = (shift(), shift());
    map {($_ + $inc) ** $power} @_;
}

sub quadratic {
    my ($a, $b, $c) = splice @_, 0, 3;
    map {$a*$_*$_ + $b*$_ + $c} @_;
}

,在极少数情况下,我需要尾部调用优化(当然是手动的),那么我必须直接使用@_,而不是短函数值得。

sub _switch    #(type, treeNode, transform[, params, ...])
{
    my $type = shift;
    my ( $treeNode, $transform ) = @_;
    unless ( defined $type ) {
        require Data::Dumper;
        die "Broken node: " . Data::Dumper->Dump( $treeNode, ['treeNode'] );
    }
    goto &{ $transform->{$type} }   if exists $transform->{$type};
    goto &{ $transform->{unknown} } if exists $transform->{unknown};
    die "Unknown type $type";
}

sub switchExTree    #(treeNode, transform[, params, ...])
{
    my $treeNode = $_[0];
    unshift @_, $treeNode->{type};    # set type
    goto &_switch;                    # tail call
}

sub switchCompact                     #(treeNode, transform[, params, ...])
{
    my $treeNode = $_[0];
    unshift @_, (%$treeNode)[0];      # set type given as first key
    goto &_switch;                    # tail call
}

sub ExTreeToCompact {
    my $tree = shift;
    return switchExTree( $tree, \%transformExTree2Compact );
}

sub CompactToExTree {
    my $tree = shift;
    return switchCompact( $tree, \%transformCompact2ExTree );
}

其中 %transformExTree2Compact 和 %transformCompact2ExTree 是哈希值,键中包含类型,值中包含代码引用,可以尾部调用 switchExTree 或 switchCompact 本身。 但这种方法很少真正需要,并且必须让那些不那么有价值的大学远离

总之,可读性和可维护性是必须的,尤其是在perl中,@_赋值在一行中更好。 如果你想设置默认值,你可以在它之后进行。

I prefer using

sub do_something_fantastical {
    my ( $foo, $bar, $baz, $qux, $quux, $corge ) = @_;
}

Because it is more readable. When this code is not called to much often it is worth way. In very rare cases you want make function called often and than use @_ directly. It is effective only for very short functions and you must be sure that this function will not evolve in future (Write once function). I this case I benchmarked in 5.8.8 that for single parameter is shift faster than $_[0] but for two parameters using $_[0] and $_[1] is faster than shift, shift.

sub fast1 { shift->call(@_) }

sub fast2 { $_[0]->call("a", $_[1]) }

But back to your question. I also prefer @_ assignment in one row over shifts for many parameters in this way

sub do_something_fantastical2 {
    my ( $foo, $bar, $baz, @rest ) = @_;
    ...
}

When Suspect @rest will not be to much big. In other case

sub raise {
    my $inc = shift;
    map {$_ + $inc} @_;
}

sub moreSpecial {
    my ($inc, $power) = (shift(), shift());
    map {($_ + $inc) ** $power} @_;
}

sub quadratic {
    my ($a, $b, $c) = splice @_, 0, 3;
    map {$a*$_*$_ + $b*$_ + $c} @_;
}

In rarely cases I need tail call optimization (by hand of course) then I must work directly with @_, than for short function is worth.

sub _switch    #(type, treeNode, transform[, params, ...])
{
    my $type = shift;
    my ( $treeNode, $transform ) = @_;
    unless ( defined $type ) {
        require Data::Dumper;
        die "Broken node: " . Data::Dumper->Dump( $treeNode, ['treeNode'] );
    }
    goto &{ $transform->{$type} }   if exists $transform->{$type};
    goto &{ $transform->{unknown} } if exists $transform->{unknown};
    die "Unknown type $type";
}

sub switchExTree    #(treeNode, transform[, params, ...])
{
    my $treeNode = $_[0];
    unshift @_, $treeNode->{type};    # set type
    goto &_switch;                    # tail call
}

sub switchCompact                     #(treeNode, transform[, params, ...])
{
    my $treeNode = $_[0];
    unshift @_, (%$treeNode)[0];      # set type given as first key
    goto &_switch;                    # tail call
}

sub ExTreeToCompact {
    my $tree = shift;
    return switchExTree( $tree, \%transformExTree2Compact );
}

sub CompactToExTree {
    my $tree = shift;
    return switchCompact( $tree, \%transformCompact2ExTree );
}

Where %transformExTree2Compact and %transformCompact2ExTree are hashes with type in key and code ref in value which can tail call switchExTree or switchCompact it selfs. But this approach is rarely really need and must keep less worth college's fingers off.

In conclusion, readability and maintainability is must especially in perl and assignment of @_ in one row is better. If you want set defaults you can do it just after it.

一桥轻雨一伞开 2024-07-17 11:43:43

恕我直言,最好的方法是两者的轻微混合,就像在模块中的新函数中一样:

our $Class;    
sub new {
    my $Class = shift;
    my %opts = @_;
    my $self = \%opts;
    # validate %opts for correctness
    ...
    bless $self, $Class;
}

然后,构造函数的所有调用参数都作为散列传递,这使得代码比参数列表更具可读性。

另外,就像 brian 说,@_ 变量是未修改的,这在异常情况下很有用。

The best way, IMHO, is a slight mixture of the two, as in the new function in a module:

our $Class;    
sub new {
    my $Class = shift;
    my %opts = @_;
    my $self = \%opts;
    # validate %opts for correctness
    ...
    bless $self, $Class;
}

Then, all calling arguments to the constructor are passed as a hash, which makes the code much more readable than just a list of parameters.

Plus, like brian said, the @_ variable is unmodified, which can be useful in unusual cases.

别靠近我心 2024-07-17 11:43:43

我怀疑如果您正在执行(粗略)相当于:

push @bar, shift @_ for (1 :: $big_number);

那么您就做错了。 我总是使用 my ($foo, $bar) = @_; 形式,因为我已经多次使用后者搬起石头砸自己的脚了......

I suspect if you're doing the (rough) equivalent of:

push @bar, shift @_ for (1 :: $big_number);

Then you're doing something wrong. I amost always use the my ($foo, $bar) = @_; form cos I've shot myself in the foot using the latter a few too many times ...

缱绻入梦 2024-07-17 11:43:43

我更喜欢的

sub factorial{
  my($n) = @_;

  ...

}

原因很简单,当我使用 Komodo 时,它能够告诉我参数是什么。

I prefer

sub factorial{
  my($n) = @_;

  ...

}

For the simple reason that Komodo will then be able to tell me what the arguments are, when I go to use it.

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