2009-02-15 14 views
11

Dans la question "Is returning a whole array from a Perl subroutine inefficient" deux personnes recommandent d'optimiser si ce n'est pas nécessaire. En règle générale, l'optimisation peut ajouter de la complexité, et si ce n'est pas nécessaire, simple est préférable. Mais dans ce cas précis, renvoyant un tableau par rapport à un tableau ref, je ne vois pas de complexité supplémentaire, et je pense que la cohérence dans la conception de l'interface serait plus importante. Par conséquent, je fais presque toujours quelque chose comme:Pourquoi ne devriez-vous PAS retourner un tableau ref?

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

    #....build up the result array ref 

    $result; 
} 

Y at-il une raison que je ne devrais pas faire cela, même pour peu de résultats?

+2

Dans "my ($ result) = [];", pourquoi les parens? – ysth

+1

Encore une fois, par souci de cohérence, je fais toujours my ($ foo); my ($ bar, $ baz); Je ne fais jamais 'my $ foo' parce que certains de mes "my's" sont dans un contexte scalaire et d'autres dans un contexte de liste. Par conséquent, je fais parfois mon ($ cnt) = scalaire (@array); qui, j'en suis sûr, conduirait certaines personnes à la folie .... –

Répondre

21

Vous ne devez pas renvoyer une référence de tableau si elle n'est pas cohérente avec le reste de votre interface. Si tout ce que vous travaillez avec retourne des listes au lieu de références, ne soyez pas le canard bizarre qui amène d'autres programmeurs à se souvenir de l'exception.

À moins d'avoir de grandes listes, c'est vraiment un problème de micro-optimisation. Vous devriez être si chanceux si c'est le goulot d'étranglement dans votre programme.

En ce qui concerne la complexité, la différence entre une référence et une liste est si faible sur l'échelle de complexité que vous avez de plus gros problèmes si vos programmeurs sont aux prises avec cela. Les algorithmes compliqués et les workflows sont complexes, mais ce n'est que de la syntaxe. Ayant dit tout cela, j'ai tendance à tout renvoyer des références et rendre les interfaces compatibles avec cela.

+1

C'était à peu près ma pensée, mais je voulais m'assurer qu'il n'y avait pas de «meilleures pratiques» répondre que je ne savais pas. –

0

Je ne suis pas sûr si retourner une référence est plus efficace dans ce cas; c'est-à-dire que Perl copie les données renvoyées par les sous-programmes?

En général, si votre tableau est entièrement construit dans le sous-programme, il n'y a pas de problème évident avec le renvoi d'une référence, sinon le tableau serait rejeté de toute façon. Cependant, si la référence est également passée ailleurs avant de la renvoyer, vous pouvez avoir deux copies de la même référence et il peut être modifié à un endroit mais pas ailleurs.

+1

Oui, perl copie les données renvoyées par les sous-programmes. – ysth

+0

Vous n'avez pas vraiment de copies de références. Les références pointent vers les mêmes données, donc si vous modifiez les données via une référence, vous verrez le changement lorsque vous y accéderez via l'autre. –

+0

Oui, par des copies de références, je voulais dire de la référence elle-même (qui peut être changée pour faire référence à quelque chose d'autre), pas des données auxquelles elle fait référence. –

7

Non. Sauf "return $ result;" pour plus de clarté. Je me souviens de tester l'efficacité de ceux-ci, et la différence de performance était minime pour les petites baies. Pour les grands tableaux, renvoyer une référence était beaucoup plus rapide.

C'est vraiment une chose de commodité pour un petit résultat. Feriez-vous plutôt ceci:

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

Ou retourner une référence:

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

Une autre façon de retourner une référence:

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

En règle générale, une fois que vous décidez quel chemin à parcourir , gardez-le pour vous module, car cela rend confus de passer d'une méthode à l'autre.

Je renvoie généralement des références de tableau pour des listes de choses similaires, et un tableau lorsque la réponse est composée de deux à quatre éléments différents. Plus que cela, je fais un hachage, car tous les appelants ne se soucient pas de tous les éléments de réponse.

+0

Voté, mais petit nitpick: Je voudrais '($ foo, $ bar) = @ {barbaz()}' – kmkaplan

0

Lorsque vous êtes habitué à utiliser le code comme premier extrait dans Mathieu Longtinanswer vous devez écrire du code laid comme deuxième extrait ou ce pas tant mieux code:

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

Je pense que c'est le plus grand inconvénient lors du retour référence au lieu de tableau. Si je veux retourner une petite quantité de valeurs différentes. Je suis habitué à retourner un tableau et à l'assigner directement aux variables (comme c'était le cas en Python par exemple).

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

Si le montant des valeurs est plus grand et divers types que je suis habitué à revenir référence de hachage (mieux refactoring)

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

Si les valeurs de retour sont de même nature, que le retour d'un tableau ou un tableau ref est discutable en fonction de la quantité.

1

Je ne pense pas que vous devriez vous sentir contraint de n'utiliser qu'une ou deux méthodes. Vous devez cependant le garder cohérent pour chaque module ou ensemble de modules.

Voici quelques exemples à méditer sur:

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

je peux voir peut-être en utilisant un grand nombre d'entre eux dans le futur projet, mais certains d'entre eux sont plutôt inutiles.

+0

Je pourrais devenir encore plus étrange, en utilisant http://search.cpan.org/perldoc?Devel::Callsite. –

0

retourne un tableau donne quelques belles prestations:

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

Si vous revenez refs tableau, vous devrez écrire plusieurs sous-marins pour faire le même travail. En outre, un tableau vide renvoie false dans un contexte booléen, mais pas un tableau vide.

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

Si vous revenez refs tableau, alors vous devez faire:

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

Sauf si get_array_ref() ne retourne pas un arbitre, dire à la place et la valeur undef, vous avez un accident arrêt de programme. L'un des suivants vous aideront à:

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

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

Donc, si les avantages de vitesse sont nécessaires ou si vous avez besoin d'une référence à un tableau (peut-être que vous voulez permettre la manipulation directe de l'élément de collecte d'un objet - beurk, mais parfois, il faut que cela arrive), retourne un tableau ref. Sinon, je trouve que les avantages des tableaux standards méritent d'être préservés.

Mise à jour: Il est vraiment important de se rappeler que ce que vous renvoyez d'une routine n'est pas toujours un tableau ou une liste. Ce que vous renvoyez est tout ce qui suit le return, ou le résultat de la dernière opération. Votre valeur de retour sera évaluée en contexte. La plupart du temps, tout ira bien, mais parfois vous pouvez avoir un comportement inattendu.

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

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

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

Comparer avec:

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

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

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

Alors, quand vous dites « retourne un tableau » assurez-vous vraiment « retourner un tableau ». Soyez conscient de ce que vous retournez de vos routines.

0

Y a-t-il une raison pour laquelle je ne devrais pas faire cela, même pour de petits résultats?

Il n'y a pas de raison spécifique à perl, ce qui signifie qu'il est correct et efficace de renvoyer une référence au tableau local. Le seul inconvénient est que les personnes qui appellent votre fonction doivent gérer le ref retourné du tableau, et accéder aux éléments avec la flèche -> ou déréférencer etc. Donc, c'est un peu plus gênant pour l'appelant.

2

Si le tableau est construit à l'intérieur de la fonction, il n'y a pas de raison de retourner le tableau; renvoyez juste une référence, puisque l'appelant est garanti qu'il y aura seulement une copie de celui-ci (il a juste été créé).

Si la fonction considère un ensemble de tableaux globaux et renvoie l'un d'entre eux, il est alors acceptable de renvoyer une référence si l'appelant ne la modifie pas. Si l'appelant peut modifier le tableau, et ce n'est pas souhaité, la fonction doit retourner une copie.

Ceci est vraiment un problème Perl unique. En Java, vous renvoyez toujours une référence, et la fonction empêche la modification du tableau (si tel est votre but) en finalisant à la fois le tableau et les données qu'il contient. En python, les références sont renvoyées et il n'y a aucun moyen d'empêcher leur modification; si c'est important, une référence à une copie est renvoyée à la place.

+0

Le problème n'est pas propre à Perl; C peut renvoyer une valeur ou une référence. Je suis sûr qu'il y a beaucoup d'autres langues qui offrent des choix similaires dans les conventions d'appel/de retour de fonction. Java et Python sont des langages OOP - les objets sont passés en références, il est donc logique qu'ils passent par référence. – daotoad

1

Je veux juste faire des commentaires sur l'idée de la syntaxe maladroite de la gestion d'une référence de tableau par opposition à une liste. Comme brian mentionné, vous ne devriez vraiment pas le faire, si le reste du système utilise des listes. C'est une optimisation inutile dans la plupart des cas. Cependant, si ce n'est pas le cas et que vous êtes libre de créer votre propre style, alors une chose qui peut rendre le code moins malodorant est l'utilisation de autobox. autobox tourne SCALAR, ARRAY et HASH (ainsi que others) en "paquets", de sorte que vous pouvez le code:

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

au lieu de un peu plus maladroit:

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

en codant quelque chose comme ça :

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

Gardez à l'esprit que autobox vous oblige à mettre en œuvre tous les comportements y Vous voulez de ceux-ci. $arr_ref->pop() ne fonctionne pas jusqu'à ce que vous définissez sub ARRAY::pop sauf si vous utilisez autobox::Core

+0

Perl 5.20 a ajouté la syntaxe postderef (expérimentale), alors maintenant vous pouvez faire quelque chose comme '$ obj-> get_arrayref -> @ [0,1]' pour obtenir cette tranche. Je trouve ça vraiment pratique. –

+0

@briandfoy, je n'ai pas encore essayé. Ça a l'air cool! Je pense que mon Perl 5.20 ressemble exactement à mon perle 5.16. : / – Axeman

6

Je vais copier la partie pertinente de ma réponse de the other question ici.

La deuxième considération souvent négligée est l'interface. Comment le tableau retourné va-t-il être utilisé? C'est important parce que le déréférencement de tableaux entiers est un peu terrible en Perl. Par exemple:

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

C'est moche. Ceci est vraiment mieux.

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

Il se prête également à la cartographie et au grepping.

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

Mais le retour d'une référence à un tableau peut être pratique si vous allez choisir les éléments individuels:

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

C'est plus simple que:

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

Ou:

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

Mais à ce stade, vous sho Uld question de savoir si @info est vraiment une liste ou un hachage. À la différence des tableaux et des références de tableaux, il y a peu de raison de choisir de renvoyer un hachage sur une référence de hachage. Les références de hachage permettent une main courte pratique, comme le code ci-dessus. Et inversement des tableaux vs refs, cela rend le cas de l'itérateur plus simple, ou évite au moins une variable d'intermédiaire.

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

Ce que vous ne devriez pas faire est d'avoir un retour getInfo() ref tableau dans un contexte scalaire et un tableau dans un contexte de liste. Cela embrouille l'utilisation traditionnelle du contexte scalaire en tant que longueur de tableau qui surprendra l'utilisateur.

Je voudrais ajouter que tout en faisant tout ce que X est une bonne règle, ce n'est pas d'une importance primordiale dans la conception d'une bonne interface. Aller un peu trop loin avec cela et vous pouvez facilement faire rouler d'autres préoccupations plus importantes.

Enfin, je vais brancher mon propre module, Method::Signatures, car il offre un compromis pour passer des références de tableau sans avoir à utiliser la syntaxe de tableau 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 

Cela se fait par la magie de Data::Alias.

1

Une omission importante dans les réponses ci-dessus: ne renvoyez pas de références à des données privées!

Par exemple:

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

Oui, les utilisateurs peuvent jeter un regard directement sous le capot avec des objets Perl mis en œuvre cette façon, mais ne le font pas facile pour les utilisateurs de se tirer dans le pied sans le vouloir, par exemple ,

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

...; 

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

Il serait mieux de retourner un (peut-être en profondeur) copie de vos données privées, comme dans

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

Comme personne mentionné au sujet de wantarray, je :-)

Je considère une bonne pratique de laisser l'appelant décider du contexte qu'il veut le résultat. Par exemple, dans le code ci-dessous, vous demandez à perl le contexte dans lequel le sous-programme a été appelé et décidez de ce qu'il doit retourner.

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

Puis

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

et

my @things = get_things(); 

fonctionne correctement en raison du contexte de la liste, et:

my $things = get_things(); 

renverra la référence du tableau. Pour plus d'informations sur wantarray, vous pouvez vérifier perldoc -f wantarray.

Edit: I sur prévoyants une des premières réponses, qui mentionnait wantarray, mais je pense que c'est la réponse est toujours valable, car il fait un peu plus clair.

Questions connexes