为什么不返回数组引用?

时间:2009-02-15 13:57:07

标签: perl arrays reference

在问题"Is returning a whole array from a Perl subroutine inefficient"中,如果不需要,两个人建议不要进行优化。作为一般规则,优化可以增加复杂性,如果不需要,简单就更好了。但是在这个特定的情况下,返回一个数组而不是数组ref,我没有看到任何增加的复杂性,我认为接口设计的一致性会更重要。因此,我几乎总是这样做:

sub foo
{
   my($result) = [];

   #....build up the result array ref

   $result;
}

我是否有理由不这样做,即使是小结果?

12 个答案:

答案 0 :(得分:23)

如果数组引用与接口的其余部分不一致,则不应返回数组引用。如果您使用的其他所有内容都返回列表而不是引用,那么不要让其他程序员记住异常。“/ p>

除非您有大型列表,否则这实际上是一个微优化问题。如果这是你的计划中的瓶颈,你应该很幸运。

就复杂性而言,参考和列表之间的差异远远低于复杂程度,如果程序员正在努力解决这个问题,那么你会遇到更大问题。复杂的算法和工作流程很复杂,但这只是语法。

说完所有这些之后,我倾向于让所有东西都返回引用并使接口与之一致。

答案 1 :(得分:7)

没有。除了“返回$ result;”为清楚起见。

我记得测试过它们的效率,小阵列的性能差异很小。对于大型数组,返回引用的速度更快。

对于小结果来说真的很方便。你愿意这样做吗:

($foo,$bar) = barbaz();

或者返回参考:

 $foobar = barbaz();
 $foobar->[0]; # $foo
 $foobar->[1]; # $bar

返回引用的另一种方法:

($foo,$bar) = @{barbaz()};

作为一项规则,一旦你决定走哪条路,就为你的模块保留它,因为从一种方法切换到下一种方法会让人感到困惑。

我通常返回类似事物列表的数组引用,以及当响应由两到四个不同元素组成时的数组。更重要的是,我做了一个哈希,因为不是所有的调用者都会关心所有的响应元素。

答案 2 :(得分:7)

我会在the other question处复制我的答案的相关部分。

经常忽略的第二个考虑因素是界面。如何使用返回的数组?这很重要,因为整个数组解除引用在Perl中有点糟糕。例如:

for my $info (@{ getInfo($some, $args) }) {
    ...
}
那是丑陋的。这要好得多。

for my $info ( getInfo($some, $args) ) {
    ...
}

它也适用于绘图和grepping。

my @info = grep { ... } getInfo($some, $args);

但是,如果要选择单个元素,则返回数组引用可能很方便:

my $address = getInfo($some, $args)->[2];

这比以下更简单:

my $address = (getInfo($some, $args))[2];

或者:

my @info = getInfo($some, $args);
my $address = $info[2];

但是在那时,你应该质疑@info是真正的列表还是哈希。

my $address = getInfo($some, $args)->{address};

与数组与数组引用不同,没有理由选择在哈希引用上返回哈希值。哈希引用允许方便的简写,就像上面的代码一样。与数组和引号相反,它使迭代器更简单,或者至少避免使用中间变量。

for my $key (keys %{some_func_that_returns_a_hash_ref}) {
    ...
}

你不应该做的是让getInfo()在标量上下文中返回一个数组引用,在列表上下文中返回一个数组。这混淆了标量上下文的传统用法,因为数组长度会让用户感到惊讶。

我想补充一点,虽然做一切事情一贯做X是一个很好的经验法则,但它在设计一个好的界面时并不是最重要的。有点太过分了,你可以轻松地解决其他更重要的问题。

最后,我将插入我自己的模块Method::Signatures,因为它提供了传递数组引用的折衷方案,而不必使用数组引用语法。

use Method::Signatures;

method foo(\@args) {
    print "@args";      # @args is not a copy
    push @args, 42;   # this alters the caller array
}

my @nums = (1,2,3);
Class->foo(\@nums);   # prints 1 2 3
print "@nums";        # prints 1 2 3 42

这是通过Data::Alias的魔力完成的。

答案 3 :(得分:2)

如果在函数内部构造数组,则没有理由返回数组;只返回一个引用,因为调用者保证只有一个副本(它刚刚创建)。

如果函数正在考虑一组全局数组并返回其中一个,那么如果调用者不修改它,则返回引用是可以接受的。如果调用者可能会修改数组并且这不是,那么该函数应该返回一个副本。

这确实是一个独特的Perl问题。在Java中,您始终返回一个引用,该函数通过最终确定数组及其包含的数据来防止数组被修改(如果这是您的目标)。在python中返回引用,并且无法阻止它们被修改;如果这很重要,则会返回对副本的引用。

答案 4 :(得分:2)

我只想评论处理数组引用的笨拙语法而不是 list 的想法。正如布莱恩所提到的,如果系统的其余部分使用列表,你真的不应该这样做。在大多数情况下,这是不必要的优化。

然而,如果情况并非如此,并且您可以自由创建自己的风格,那么可以使编码不那么臭的一件事就是使用autoboxautoboxSCALARARRAYHASH(以及others)变为“包”,以便您可以编码:

my ( $name, $number ) = $obj->get_arrayref()->items( 0, 1 );

而不是稍微笨拙:

my ( $name, $number ) = @{ $obj->get_arrayref() };

通过这样的编码:

sub ARRAY::slice { 
    my $arr_ref = shift;
    my $length  = @$arr_ref;
    my @subs    = map { abs($_) < $length ? $_ : $_ < 0 ? 0 : $#$arr_ref } @_;
    given ( scalar @subs ) { 
        when ( 0 ) { return $arr_ref; }
        when ( 2 ) { return [ @{$arr_ref}[ $subs[0]..$subs[1] ] ]; }
        default    { return [ @{$arr_ref}[ @subs ] ]; }
    }
    return $arr_ref; # should not get here.
}

sub ARRAY::items { return @{ &ARRAY::slice }; }

请注意,autobox要求您实施所需的所有行为。除非您使用autobox::Core

,否则在定义$arr_ref->pop()之前,sub ARRAY::pop才会生效

答案 5 :(得分:2)

由于没有人提及wantarray,我会: - )

我认为让调用者决定它想要结果的上下文是一个好习惯。例如,在下面的代码中,你要求perl调用子例程的上下文并决定返回什么。

sub get_things {
    my @things;
    ... # populate things
    return wantarray ? @things : \@things;
}

然后

for my $thing ( get_things() ) {
    ...
}

my @things = get_things();
由于列表上下文,

正常工作,并且:

my $things = get_things();

将返回数组的引用。

有关wantarray的详细信息,您可以查看perldoc -f wantarray

修改:我首先回答了其中一个提到wantarray的答案,但我认为答案仍然有效,因为它使它更清晰。

答案 6 :(得分:1)

我认为你不应该仅仅使用一种或两种方法。但是,您应该为每个模块或模块集保持一致。

以下是一些需要思考的例子:

sub test1{
  my @arr;
  return @arr;
}
sub test2{
  my @arr;
  return @arr if wantarray;
  return \@arr;
}
sub test3{
  my %hash;
  return %hash;
}
sub test4{
  my %hash;
  return %hash if wantarray;
  return \%hash;
}
sub test5{
  my %hash;
  return $hash{ qw'one two three' } if wantarray;
  return \%hash;
}
{
  package test;
  use Devel::Caller qw'called_as_method';
  sub test6{
    my $out;
    if( wantarray ){
      $out = 'list';
    }else{
      $out = 'scalar';
    }
    $out = "call in $out context";
    if( called_as_method ){
      $out = "method $out";
    }else{
      $out = "simple function $out";
    }
    return $out;
  }
}

我可以看到在未来的项目中可能会使用其中的许多,但其中一些是毫无意义的。

答案 7 :(得分:1)

上述答案中的一个重要遗漏:不要返回对私人数据的引用!

例如:

package MyClass;

sub new {
  my($class) = @_;
  bless { _things => [] } => $class;
}

sub add_things {
  my $self = shift;
  push @{ $self->{_things} } => @_;
}

sub things {
  my($self) = @_;
  $self->{_things};  # NO!
}

是的,用户可以通过这种方式实现Perl对象直接窥视,但是不要让用户轻易地在脚下自我射击,例如

my $obj = MyClass->new;
$obj->add_things(1 .. 3);

...;

my $things = $obj->things;
my $first = shift @$things;

最好是返回私人数据的(可能很深的)副本,如

sub things {
  my($self) = @_;
  @{ $self->{_things} };
}

答案 8 :(得分:0)

我不确定在这种情况下返回引用是否更有效;即Perl是否复制子程序返回的数据?

通常,如果您的数组完全在子例程中构造,则返回引用没有明显的问题,否则无论如何都会丢弃该数组。但是,如果引用也在返回之前传递到其他位置,则可能有两个相同引用的副本,并且可能会在一个位置进行修改,但不会在其他地方进行修改。

答案 9 :(得分:0)

当您习惯使用代码作为Mathieu Longtin answer中的第一个代码段时,您必须将丑陋的代码编写为第二个代码段,或者这不是更好的代码:

my ($foo,$bar) = @{barbaz()};

我认为这是返回引用而不是数组时的最大缺点。如果我想要返回少量不同类型的值。我习惯于返回数组并直接赋值给变量(例如在Python中用过)。

my ($status, $result) = do_something();
if ($status eq 'OK') {
    ...

如果值的数量更大且各种类型我用于返回散列引用(更好的重构)

my ($status, $data, $foo, $bar, $baz) =
    @{do_something()}{qw(status data foo bar baz)};
if ($status eq 'OK') {
    ...

如果返回值是同一种类型,则返回数组或数组ref是有争议的,具体取决于数量。

答案 10 :(得分:0)

返回数组会带来一些好处:

my @foo = get_array(); # Get list and assign to array.
my $foo = get_array(); # Get magnitude of list.
my ($f1, $f2) = get_array(); # Get first two members of list.
my ($f3,$f6) = (get_array())[3,6]; # Get specific members of the list.

sub get_array {
   my @array = 0..9;

   return @array;
}

如果返回数组引用,则必须编写几个subs来执行相同的工作。此外,空数组在布尔上下文中返回false,但空数组ref不会。

if ( get_array() ) {
    do_stuff();
}

如果你返回数组引用,那么你必须这样做:

if ( @{ get_array_ref() } ) {
    do_stuff();
}

除非get_array_ref()无法返回ref,例如,而不是undef值,否则你有一个程序停止崩溃。以下之一将有所帮助:

if ( @{ get_array() || [] } ) {
    do_stuff();
}

if ( eval{ @{get_array()} } ) {
    do_stuff();
}

因此,如果需要速度优势,或者如果您需要数组引用(也许您希望允许直接操作对象的集合元素 - yuck,但有时必须发生),请返回数组引用。否则,我发现值得保留的标准数组的好处。

更新:要记住,从例程返回的内容并不总是数组或列表,这一点非常重要。您返回的是return之后的任何内容,或上一次操作的结果。您的返回值将在上下文中进行评估。大多数时候,一切都会好起来的,但有时你会遇到意想不到的行为。

sub foo {
    return $_[0]..$_[1];
}

my $a = foo(9,20);
my @a = foo(9,20);

print "$a\n";
print "@a\n";

与:比较:

sub foo {
    my @foo = ($_[0]..$_[1]);
    return @foo;
}

my $a = foo(9,20);
my @a = foo(9,20);

print "$a\n";
print "@a\n";

所以,当你说“返回数组”时,请确保你的意思是“返回数组”。注意你从日常生活中回来的东西。

答案 11 :(得分:0)

我是否有理由不这样做,即使是小结果?

没有perl特定的原因,这意味着返回对本地数组的引用是正确和有效的。唯一的缺点是调用你的函数的人必须处理返回的数组ref,并使用箭头->或解除引用等访问元素。因此,对于调用者来说,它稍微麻烦一些。