15

让我们暂时忽略 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欢迎有充分理由的意见。

4

9 回答 9

28

有功能上的区别。shift会修改,而 from的@_赋值@_不会。如果您以后不需要使用@_,那么这种差异对您来说可能并不重要。我尝试始终使用列表分配,但有时我使用shift.

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

 my( $param ) = shift;

我经常创建这个错误:

 my( $param, $other_param ) = shift;

那是因为我不shift经常使用它,所以我忘记到作业的右侧将其更改为@_. 这就是不使用shift. shift我可以像您在示例中所做的那样为每个行单独制作,但这很乏味。

于 2009-01-06T06:54:26.063 回答
16

至少在我的系统上,它似乎取决于 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 是否进行了任何特殊类型的优化。你的旅费可能会改变。

保罗

于 2009-01-06T05:50:39.093 回答
8

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

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

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

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

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

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

于 2009-01-06T03:50:30.163 回答
4

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

sub do_something_fantastical {
    my $foo   = shift || die("must have foo");
    my $bar   = shift || 0;  # $bar is optional
    # ...
}
于 2009-01-06T03:57:00.603 回答
3

我更喜欢将 @_ 解压缩为列表(您的第二个示例)。不过,就像 Perl 中的所有内容一样,在某些情况下使用 shift 可能很有用。例如,旨在在基类中被覆盖的 passthru 方法,但您想确保如果它们没有被覆盖,它们仍然可以工作。


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

于 2009-01-06T04:23:59.300 回答
2

我更喜欢使用

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

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

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

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

但回到你的问题。我也更喜欢以这种方式在一行中对许多参数进行@_赋值

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

当 Suspect @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 是具有 key 类型和 value 中的 code ref 的哈希值,可以尾调用 switchExTree 或 switchCompact 它自己。但是这种方法很少真正需要,并且必须让不太值得的大学动用手指

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

于 2009-01-06T09:54:10.833 回答
1

恕我直言,最好的方法是两者的轻微混合,如模块中的新功能:

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

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

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

于 2009-07-21T03:42:05.387 回答
0

我怀疑你是否正在做(粗略的)相当于:

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

那么你做错了什么。我几乎总是使用这种my ($foo, $bar) = @_;形式,因为我用后者多次射击自己的脚......

于 2009-02-18T10:53:17.613 回答
0

我更喜欢

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

  ...

}

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

于 2009-07-21T03:07:12.240 回答