Подтвердить что ты не робот

Почему вы не должны возвращать массив ref?

В вопросе "Возвращает целый массив из подпрограммы Perl неэффективно" , два человека рекомендуют против оптимизации, если в этом нет необходимости. Как правило, оптимизация может добавить сложность, и, если это не понадобится, просто лучше. Но в этом конкретном случае, возвращая массив по сравнению с массивом ref, я не вижу, чтобы была добавлена ​​сложность, и я думаю, что более важна согласованность в дизайне интерфейса. Следовательно, я почти всегда делаю что-то вроде:

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

   #....build up the result array ref

   $result;
}

Есть ли причина, по которой я не должен этого делать, даже для небольших результатов?

4b9b3361

Ответ 1

Вы не должны возвращать ссылку на массив, если она не соответствует остальной части вашего интерфейса. Если все остальное, что вы работаете с списками возвратов вместо ссылок, не будьте нечетной уткой, которая заставляет других программистов помнить об исключении.

Если у вас нет больших списков, это действительно проблема микро-оптимизации. Вам должно быть повезло, если это узкое место в вашей программе.

Что касается сложности, разница между ссылкой и списком настолько далека от масштаба сложности, что у вас большие проблемы, если ваши программисты борются с этим. Сложные алгоритмы и рабочие процессы сложны, но это всего лишь синтаксис.

Сказав все это, я стараюсь, чтобы все возвращало ссылки и совместило интерфейсы с этим.

Ответ 2

Нет. За исключением "return $result"; для ясности.

Я помню, как тестировал эффективность, и разница в производительности была минимальной для небольших массивов. Для больших массивов возвращение ссылки было быстрее.

Это действительно удобная вещь для небольшого результата. Вы предпочли бы это сделать:

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

Или вернуть ссылку:

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

Другой способ вернуть ссылку:

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

Как правило, после того, как вы решите, куда идти, просто держитесь за него для своего модуля, так как он сбивает с толку переход от одного метода к другому.

Я обычно возвращаю ссылки массива для списков подобных вещей и массив, когда ответ состоит из двух-четырех разных элементов. Более того, я делаю хэш, так как не все вызывающие будут заботиться обо всех элементах ответа.

Ответ 3

Я скопирую соответствующую часть своего ответа из другого вопроса здесь.

Частое игнорирование второго аспекта - интерфейс. Как будет использоваться возвращаемый массив? Это важно, потому что разыменование целых массивов в Perl выглядит ужасно. Например:

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

Это уродливо. Это намного лучше.

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

Он также поддается отображению и grepping.

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

Но возврат массива ref может быть удобным, если вы собираетесь выбирать отдельные элементы:

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

В отличие от массивов vs array refs, есть небольшая причина для выбора хэша по хэш-ref. Хэш-ссылки позволяют использовать короткие руки, например, код выше. И напротив массивов vs refs, он делает итератор более простым или, по крайней мере, избегает переменной среднего человека.

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

То, что вам не следует делать, это getInfo() вернуть массив ref в скалярном контексте и массив в контексте списка. Это путает традиционное использование скалярного контекста как длины массива, что удивит пользователя.

Я хотел бы добавить, что, делая все последовательно, X - хорошее эмпирическое правило, это не имеет первостепенного значения при разработке хорошего интерфейса. Пойдите немного слишком далеко, и вы можете легко справиться с другими важными проблемами.

Наконец, я подключу свой собственный модуль, Method::Signatures, потому что он предлагает компромисс для передачи ссылок на массивы без использования синтаксис ref массива.

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.

Ответ 4

Если массив построен внутри функции, нет причин возвращать массив; просто верните ссылку, поскольку вызывающему абоненту гарантировано, что будет только одна его копия (она была только что создана).

Если функция рассматривает набор глобальных массивов и возвращает один из них, то приемлемо возвращать ссылку, если вызывающий абонент не будет ее изменять. Если вызывающий может изменить массив, и это нежелательно, функция должна вернуть копию.

Это действительно уникальная проблема Perl. В Java вы всегда возвращаете ссылку, а функция предотвращает изменение массива (если это ваша цель), завершая как массив, так и данные, которые он содержит. В ссылках на python возвращаются, и нет возможности предотвратить их изменение; если это важно, вместо этого возвращается ссылка на копию.

Ответ 5

Я не думаю, что вы должны сдерживать использование только одного или двух методов. Тем не менее, вы должны сохранять его согласованным для каждого модуля или набора модулей.

Вот несколько примеров, чтобы обдумать:

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

Я вижу, возможно, используя многие из них в будущем проекте, но некоторые из них довольно бессмысленны.

Ответ 6

Я просто хочу прокомментировать идею о неуклюжем синтаксисе обработки ссылки на массив, а не в списке. Как сказал Брайан, вы действительно не должны этого делать, если остальная система использует списки. В большинстве случаев это ненужная оптимизация.

Однако, если это не так, и вы можете создать свой собственный стиль, то одна вещь, которая может сделать кодирование менее вонючей, использует autobox. autobox превращает SCALAR, ARRAY и HASH (а также другие) в "пакеты", чтобы вы могли код:

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 требует, чтобы вы реализовали все поведение, которое вы хотите от них. $arr_ref->pop() не работает, пока вы не определите sub ARRAY::pop, если вы не используете autobox::Core

Ответ 7

Важное упущение в приведенных выше ответах: не возвращайте ссылки на личные данные!

Например:

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

Я не уверен, что возвращение ссылки более эффективно в этом случае; то есть данные Perl, возвращаемые подпрограммами?

В общем случае, если ваш массив сконструирован полностью внутри подпрограммы, тогда нет никакой очевидной проблемы с возвратом ссылки, потому что иначе массив будет отброшен в любом случае. Однако, если ссылка также передается в другом месте перед ее возвратом, у вас могут быть две копии одной и той же ссылки, и она может быть изменена в одном месте, но не ожидается в другом месте.

Ответ 9

Когда вы привыкли использовать код в качестве первого фрагмента в Mathieu Longtin answer, вам нужно писать уродливые кода в качестве второго фрагмента или этого не намного лучшего кода:

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

Я думаю, что это самый большой недостаток при возврате ссылки вместо массива. Если я хочу вернуть небольшое количество разных значений. Я использую, чтобы возвращать массив и назначать напрямую переменные (как это обычно используется в Python).

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

Если количество значений больше и различный вид, я использую для возврата хэша ref (лучше для рефакторинга)

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

Если возвращаемые значения имеют один и тот же вид, то возвращение массива или массива ref является дискуссионным в зависимости от суммы.

Ответ 10

Возврат массива дает несколько приятных преимуществ:

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

Если вы возвращаете массив refs, вам нужно будет написать несколько подписчиков для выполнения той же работы. Кроме того, пустой массив возвращает false в булевом контексте, но пустой массив ref не делает.

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

Если вы возвращаете массив refs, вам нужно сделать:

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

За исключением того, что get_array_ref() не может вернуть ref, скажем вместо этого и значение undef, у вас есть остановка программы. Одно из следующих действий поможет:

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

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

Итак, если нужны преимущества скорости или вам нужен массив ref (возможно, вы хотите разрешить прямое управление элементом коллекции объектов - yuck, но иногда это должно произойти), верните массив ref. В противном случае я нахожу преимущества стандартных массивов, которые стоит сохранить.

Обновление: Очень важно помнить, что то, что вы возвращаете из подпрограммы, не всегда является массивом или списком. То, что вы возвращаете, это то, что следует за 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

Есть ли причина, по которой я не должен этого делать, даже для небольших результатов?

Там нет специфической для perl причины, что означает правильность и эффективность возврата ссылки на локальный массив. Единственным недостатком является то, что люди, которые называют вашу функцию, должны иметь дело с возвращаемым массивом ref и получать доступ к элементам со стрелкой -> или разыменованием и т.д. Таким образом, это немного более неприятно для вызывающего.

Ответ 12

Поскольку никто не упоминал о 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, но я думаю, что это ответ по-прежнему действителен, потому что он делает его более понятным.