2009-02-15 7 views
11

En la pregunta "Is returning a whole array from a Perl subroutine inefficient" dos personas recomiendan no optimizar si no es necesario. Como regla general, la optimización puede agregar complejidad, y si no es necesaria, lo simple es mejor. Pero en este caso específico, al devolver una matriz en lugar de una matriz de referencia, no veo que haya una complejidad adicional, y creo que la consistencia en el diseño de la interfaz sería más importante. En consecuencia, casi siempre hago algo como:¿Por qué NO deberías devolver una referencia de matriz?

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

    #....build up the result array ref 

    $result; 
} 

¿Hay alguna razón por la que no deba hacer esto, incluso por pequeños resultados?

+2

En "mi ($ resultado) = [];", ¿por qué los parens? – ysth

+1

Una vez más, por consistencia, siempre hago mi ($ foo); mi ($ bar, $ baz); Nunca hago 'my $ foo' porque entonces algunos de mis "my's" están en un contexto escalar y algunos están en un contexto de lista. En consecuencia, a veces hago mi ($ cnt) = escalar (@array); que estoy seguro volvería loca a algunas personas .... –

Respuesta

21

No debe devolver una referencia de matriz si no es coherente con el resto de la interfaz. Si todo lo demás con lo que trabajas devuelve listas en lugar de referencias, no seas el extraño que causa que otros programadores recuerden la excepción.

A menos que tenga grandes listas, esto es realmente un problema de micro-optimización. Deberías tener tanta suerte si este es el cuello de botella en tu programa.

En lo que respecta a la complejidad, la diferencia entre una referencia y una lista es tan baja en la escala de complejidad que usted tiene mayores problemas si los programadores tienen problemas con eso. Los algoritmos y flujos de trabajo complicados son complejos, pero esto es solo una sintaxis.

Habiendo dicho todo eso, tiendo a hacer que todo muestre referencias y establezca interfaces consistentes con eso.

+1

Eso era más o menos mi pensamiento, pero quería asegurarme de que no hubiera una "Mejores prácticas" respuesta que yo no sabía –

0

No estoy seguro si devolver una referencia es más eficiente en este caso; es decir, ¿Perl copia datos devueltos por subrutinas?

En general, si su matriz se construye completamente dentro de la subrutina, entonces no hay un problema obvio al devolver una referencia porque de lo contrario la matriz se descartaría de todos modos. Sin embargo, si la referencia también se pasa a otra parte antes de devolverla, puede tener dos copias de la misma referencia y puede modificarse en un lugar pero no se espera que lo haga en otro lugar.

+1

Sí, perl copia los datos devueltos por las subrutinas. – ysth

+0

Realmente no tiene copias de referencias. Las referencias apuntan a los mismos datos, de modo que si cambia los datos a través de una referencia, verá el cambio cuando acceda a través de la otra. –

+0

Sí, por copias de referencias, quise decir de la referencia en sí (que se puede cambiar para referirse a otra cosa), no de los datos a los que hace referencia. –

7

No. Excepto "return $ result;" para mayor claridad.

Recuerdo probar la eficiencia de aquellos, y la diferencia en el rendimiento fue mínima para arreglos pequeños. Para arreglos grandes, devolver una referencia era mucho más rápido.

Es realmente una conveniencia para pequeños resultados. ¿Prefiere hacer esto:

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

o devolver una referencia:

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

Otra forma de devolver una referencia:

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

Como regla general, una vez que decida que camino por recorrer , solo guárdelo para su módulo, ya que hace que sea confuso cambiar de un método al siguiente.

Normalmente devuelvo referencias de matriz para listas de cosas similares, y una matriz cuando la respuesta se compone de dos a cuatro elementos diferentes. Más que eso, hago un hash, ya que no todos los que llaman se preocupan por todos los elementos de respuesta.

+0

Votado, pero pequeño detalle: me gustaría '($ foo, $ bar) = @ {barbaz()}' – kmkaplan

0

Cuando estás acostumbrado a utilizar código como el primer fragmento de Mathieu Longtinanswer usted tiene que escribir código feo como el segundo fragmento o este no es tanto un mejor código:

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

Creo que este es el mayor inconveniente al regresar referencia en lugar de matriz. Si quiero devolver una pequeña cantidad de diferentes valores de tipo. Estoy acostumbrado a devolver el conjunto y asignarlo directamente a las variables (como solía hacerlo en Python, por ejemplo).

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

Si cantidad de valores es más grande y diversos tipos Estoy acostumbrado a volver ref almohadilla (mejor para refactorización)

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

Si los valores de retorno son del mismo tipo, de volver de la matriz o una matriz ref es discutible dependiendo de la cantidad.

1

No creo que deba sentirse obligado a usar solo uno o dos métodos. Sin embargo, debe mantenerlo constante para cada módulo o conjunto de módulos.

Éstos son algunos ejemplos para reflexionar sobre: ​​

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

puedo ver posiblemente usando muchos de estos en el futuro proyecto, pero algunos de ellos son bastante inútil.

+0

Podría ser aún más extraño, usando http://search.cpan.org/perldoc?Devel::Callsite. –

0

Retornando una matriz da algunos beneficios bonito:

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 regresa árbitros de matriz, que tendrá que escribir varios submarinos que hacer el mismo trabajo. Además, una matriz vacía devuelve falso en un contexto booleano, pero una matriz vacía ref no.

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

Si regresa árbitros de la matriz, entonces usted tiene que hacer:

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

Excepto si get_array_ref() no puede devolver una referencia, por ejemplo en lugar y el valor undef, que tiene un programa de detención de caída. Uno de los siguientes le ayudarán:

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

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

lo tanto, si se necesitan los beneficios de velocidad o si necesita una ref array (tal vez se quiere permitir la manipulación directa del elemento de la colección de un objeto - ¡qué asco, pero a veces tienen que suceder), devuelve una matriz ref. De lo contrario, me parece que vale la pena conservar los beneficios de las matrices estándar.

Actualización: Es muy importante recordar que lo que devuelve una rutina no siempre es una matriz o una lista. Lo que devuelve es lo que sigue al return, o el resultado de la última operación. Su valor de retorno será evaluado en contexto. La mayoría de las veces, todo estará bien, pero a veces puede tener un comportamiento inesperado.

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

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

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

Comparar con:

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

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

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

Por lo tanto, cuando se dice "devolver una matriz" estar seguro de que realmente significa "devolver una matriz". Se consciente de lo que regresas de tus rutinas.

0

¿Hay alguna razón por la que no deba hacer esto, incluso por pequeños resultados?

No hay un motivo específico de perl, lo que significa que es correcto y eficiente devolver una referencia a la matriz local. El único inconveniente es que las personas que llaman a su función tienen que tratar con la referencia de matriz devuelta, y acceder a los elementos con la flecha -> o desreferencia, etc. Por lo tanto, es un poco más problemático para la persona que llama.

2

Si la matriz se construye dentro de la función, no hay razón para devolver la matriz; simplemente devuelva una referencia, ya que a la persona que llama se le garantiza que solo habrá una copia (se acaba de crear).

Si la función considera un conjunto de matrices globales y devuelve una de ellas, entonces es aceptable devolver una referencia si la persona que llama no la modificará. Si la persona que llama puede modificar la matriz, y no se desea, la función debe devolver una copia.

Esto realmente es un problema único de Perl. En Java, siempre devuelve una referencia, y la función evita que la matriz se modifique (si ese es su objetivo) finalizando tanto la matriz como los datos que contiene. En Python, se devuelven las referencias y no hay forma de evitar que se modifiquen; si eso es importante, se devuelve una referencia a una copia.

+0

El problema no es exclusivo de Perl; C puede devolver un valor o una referencia. Estoy seguro de que hay muchos otros idiomas que ofrecen opciones similares en las convenciones de llamada/devolución de funciones. Java y Python son lenguajes OOP: los objetos se pasan como referencias, por lo que tiene sentido que pasen por referencia. – daotoad

1

Solo quiero comentar sobre la idea de la sintaxis torpe de manejar una referencia de matriz en oposición a una lista de . Como lo mencionó Brian, realmente no deberías hacerlo, si el resto del sistema está usando listas. Es una optimización innecesaria en la mayoría de los casos.

Sin embargo, si ese no es el caso, y usted es libre de crear su propio estilo, una cosa que puede hacer que la codificación sea menos apestosa es usando autobox. autobox convierte SCALAR, ARRAY y HASH (así como others) en "paquetes", de tal manera que se puede código:

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

en lugar de los poco más torpe:

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

codificando algo como esto :

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

Tenga en cuenta que autobox requiere a implementar todos los comportamientos y quieres de estos. $arr_ref->pop() no funciona hasta que defina sub ARRAY::pop a menos que utilice autobox::Core

+0

Perl 5.20 agregó la sintaxis postderef (experimental), por lo que ahora puede hacer algo como '$ obj-> get_arrayref -> @ [0,1]' para obtener esa porción. Lo estoy encontrando realmente útil. –

+0

@briandfoy, aún no lo he intentado. ¡Se ve bien! Creo que mi 5.20 Perl se ve exactamente como mi 5.16 perl. : / – Axeman

6

voy a copiar la parte pertinente de mi respuesta de the other question aquí.

La segunda consideración que a menudo se pasa por alto es la interfaz. ¿Cómo se usará la matriz devuelta? Esto es importante porque la desreferenciación de arreglos completos es algo horrible en Perl. Por ejemplo:

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

Eso es feo. Esto es mucho mejor.

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

También se presta para mapeo y grepping.

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

Pero volviendo una ref matriz puede ser útil si usted va a seleccionar elementos individuales:

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

Eso es más simple que:

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

O:

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

Pero en ese punto, usted sho uld pregunta si @info es realmente una lista o un hash.

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

A diferencia de las matrices vs árbitros de la matriz, hay pocas razones para elegir a devolver un hash de más de un ref hash. Los Hash ref permiten manejos cortos, como el código anterior. Y al contrario de las matrices frente a las referencias, hace que el caso del iterador sea más simple, o al menos evita una variable del intermediario.

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

Lo que no debe hacer es tener getInfo() devolver una referencia array en contexto escalar y una matriz en el contexto de lista. Esto confunde el uso tradicional del contexto escalar como la longitud de la matriz que sorprenderá al usuario.

Me gustaría agregar que, si bien hacer todo consistentemente, X es una buena regla empírica, no es de suma importancia diseñar una buena interfaz. Llegue un poco lejos con esto y fácilmente puede alterar otras preocupaciones más importantes.

Finalmente, conectaré mi propio módulo, Method::Signatures, porque ofrece un compromiso para pasar referencias de matriz sin tener que utilizar la sintaxis de matriz de 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 

Esto se hace a través de la magia de Data::Alias.

1

Una omisión importante en las respuestas anteriores: ¡no devuelva referencias a datos privados!

Por ejemplo:

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

Sí, los usuarios pueden mirar directamente debajo de la campana con Perl objetos implementados esta manera, pero no hacen que sea fácil para los usuarios disparar sin darse cuenta en el pie, por ejemplo, ,

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

...; 

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

mejor sería devolver una copia (tal vez de profundidad) de sus datos privados, como en

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

Ya que nadie ha mencionado acerca wantarray, yo :-)

Considero una buena práctica dejar que la persona que llama decida en qué contexto desea el resultado. Por ejemplo, en el siguiente código, pregunta a Perl por el contexto al que se llamó la subrutina y decide qué devolver.

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

Entonces

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

y

my @things = get_things(); 

funciona adecuadamente debido al contexto de la lista, y:

my $things = get_things(); 

volverá referencia de la matriz. Para obtener más información acerca de wantarray, puede consultar perldoc -f wantarray.

Editar: I sobre previsora ​​una de las primeras respuestas, que mencionan wantarray, pero creo que esta es la respuesta sigue siendo válida, ya que hace que sea un poco más claro.

Cuestiones relacionadas