2009-02-15 2 views
11

В вопросе "Is returning a whole array from a Perl subroutine inefficient" два человека рекомендуют против оптимизации, если в этом нет необходимости. Как правило, оптимизация может добавить сложность, и если она не нужна, проще будет лучше. Но в этом конкретном случае, возвращая массив против массива ref, я не вижу, что есть какая-то дополнительная сложность, и я думаю, что согласованность в дизайне интерфейса была бы более важной. Следовательно, я почти всегда делаю что-то вроде:Зачем вам НЕ возвращать массив ref?

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

    #....build up the result array ref 

    $result; 
} 

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

+2

В "my ($ result) = [];", почему parens? – ysth

+1

Опять же, для последовательности, я всегда делаю my ($ foo); my ($ bar, $ baz); Я никогда не делаю «мой $ foo», потому что некоторые из моих «моих» находятся в скалярном контексте, а некоторые из них находятся в контексте списка. Следовательно, я иногда делаю my ($ cnt) = scalar (@array); , который, я уверен, привел бы людей в бешенство .... –

ответ

21

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

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

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

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

+1

Это было в значительной степени моим мнением, но я хотел убедиться, что не было «лучшей практики», ответ, о котором я не знал. –

0

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

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

+1

Да, perl копирует данные, возвращаемые подпрограммами. – ysth

+0

У вас действительно нет копий ссылок. Ссылки указывают на одни и те же данные, поэтому, если вы измените данные с помощью одной ссылки, вы увидите изменение при доступе к нему через другое. –

+0

Да, по копиям ссылок, я имел в виду собственно ссылку (которую можно изменить, чтобы ссылаться на что-то еще), а не на данные, на которые она ссылается. –

7

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

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

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

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

Или возвращает ссылку:

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

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

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

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

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

+0

Проголосовал, но немного nitpick: Я бы '($ foo, $ bar) = @ {barbaz()}' – kmkaplan

0

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

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 является спорным в зависимости от суммы.

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

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

+0

Я мог бы стать еще более странным, используя http://search.cpan.org/perldoc?Devel::Callsite. –

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

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

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

Если вы вернетесь рефами массива, то вы должны сделать:

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

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

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

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

Так, если преимущества скорости необходимы или, если вам нужен реф массив (возможно, вы хотите, чтобы позволить прямое манипулирование элемента коллекции объекта - гадость, но иногда это должно произойти), верните массив 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"; 

Итак, когда вы говорите, «вернуть массив» убедитесь, что вы на самом деле означает «вернуть массив». Имейте в виду, что вы возвращаетесь из своих подпрограмм.

0

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

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

2

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

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

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

+0

Проблема не уникальна для Perl; C может вернуть значение или ссылку. Я уверен, что есть много других языков, которые предлагают аналогичный выбор в соглашениях о вызовах/возврате функций. Java и Python являются языками ООП - объекты передаются как ссылки, поэтому имеет смысл передавать их по ссылке. – daotoad

1

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

Однако, если это не так, и вы можете создать свой собственный стиль, то одна вещь, которая может сделать кодирование менее вонючей, - это использование autobox. autobox превращает SCALAR, ARRAY и HASH (а также 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 требует от вас осуществлять все виды поведения у ou хочу от них. $arr_ref->pop() не работает, пока вы не определите sub ARRAY::pop, если вы используете autobox::Core

+0

Perl 5.20 добавил postderef (экспериментальный) синтаксис, поэтому теперь вы можете сделать что-то вроде '$ obj-> get_arrayref -> @ [0,1]', чтобы получить этот фрагмент. Мне это очень удобно. –

+0

@briandfoy, еще не пробовал. Выглядит круто! Я думаю, что мой 5.20 Perl выглядит точно так же, как мой 5.16 perl. : / – Axeman

6

Я скопировать соответствующую часть моего ответа от 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]; 

Но в этот момент вы шо uld вопрос, является ли @info действительно списком или хешем.

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

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

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

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

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

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

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

Поскольку никто не упомянул о 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.

Edit: я над дальновидным один из первых ответов, которые упомянутые wantarray, но я думаю, что это ответ остается в силе, потому что это делает его немного яснее.

Смежные вопросы