2010-01-13 7 views
17

OK, tengo el siguiente código:¿Por qué Perl foreach variable assignment modifica los valores en la matriz?

use strict; 
my @ar = (1, 2, 3); 
foreach my $a (@ar) 
{ 
    $a = $a + 1; 
} 

print join ", ", @ar; 

y la salida?

2, 3, 4

¿Qué diablos? ¿Porque hace eso? ¿Esto siempre sucederá? ¿$ a no es realmente una variable local? ¿En qué piensan?

+12

Fwiw, este comportamiento se documenta en la página de manual perlsyn: http://perldoc.perl.org/perlsyn.html#Foreach-Loops – friedo

+6

pensar en él como declarar un iterador. En básicamente cada idioma que implementa iteradores, puede usar el iterador para modificar el valor existente. Si quieres copiar la semántica, solo haz una copia. :) –

Respuesta

23

Perl tiene muchas de estas cosas de sintaxis casi extrañas que simplifican en gran medida las tareas comunes (como iterar sobre una lista y cambiar el contenido de alguna manera), pero te pueden hacer tropezar si no estás enterado.

$a tiene un alias al valor en la matriz - esto le permite modificar la matriz dentro del ciclo. Si no quiere hacer eso, no modifique $a.

+0

Gracias por explicar por qué decidieron hacerlo de esta manera. – tster

9

$a en este caso es un alias para el elemento de matriz. Simplemente no tiene $a = en su código y no modificará la matriz. :-)

Si no recuerdo mal, map, grep, etc. todos tienen el mismo comportamiento de aliasing.

22

Ver perldoc perlsyn:

Si ningún elemento de lista es un valor-I, se puede modificar mediante la modificación de VAR dentro del bucle. Por el contrario, si algún elemento de LIST no es un valor l, cualquier intento de modificar ese elemento fallará. En otras palabras, la variable de índice de bucle foreach es un alias implícito para cada elemento de la lista sobre el que está pasando el bucle.

No hay nada raro o impar sobre un documentado función lengua aunque yo lo encuentro extraño que muchas personas se niegan comprobar la documentación al encontrarse con un comportamiento que no entienden.

+4

+1 para "Me resulta extraño cuántas personas se niegan a leer cualquier documentación". :-P –

+16

¿Por qué algo no se puede considerar extraño incluso si está documentado? ¿Y si la especificación del lenguaje dijera que se ejecutará una declaración única después de la declaración de devolución, pero no más de 1. Sería extraño y documentado? Raro es una cuestión de opinión. – tster

3

la distinción importante aquí es que cuando se declara una variable my en la sección de inicialización de un bucle for, parece compartir algunas propiedades de los habitantes locales y lexicals (alguien con más conocimiento de los cuidados internos para aclarar?)

my @src = 1 .. 10; 

for my $x (@src) { 
    # $x is an alias to elements of @src 
} 

for (@src) { 
    my $x = $_; 
    # $_ is an alias but $x is not an alias 
} 

el efecto secundario interesante de esto es que en el primer caso, un sub{} definido dentro del bucle for es un cierre alrededor de cualquier elemento de la lista $x fue un alias a. Sabiendo esto, es posible (aunque un poco extraño) cerrar alrededor de un valor de alias que incluso podría ser un global, que no creo que sea posible con ningún otro constructo.

our @global = 1 .. 10; 
my @subs; 
for my $x (@global) { 
    push @subs, sub {++$x} 
} 

$subs[5](); # modifies the @global array 
+0

Re: "No creo que sea posible con ningún otro constructo". Seguramente has oído hablar del lema de Perl TIMTOWTDI (http://en.wikipedia.org/wiki/TIMTOWTDI). Obtenga una referencia a un elemento de matriz: 'my $ g5r = \ $ global [5]; $ {$ g5r} ++; '. Hacer un alias con un global: 'our $ g5; * g5 = \ $ global [5]; $ g5 ++; '. Localizándolo y usando un sub: 'sub doSubOnRef (& \ $) {local $ _; * _ = $ _ [1]; $ _ [0] ->()} doSubOnRef {$ _ ++} $ global [5]; '. Comprobando que todos se refieren a lo mismo: 'mi @equality = mapa {$ _ == \ $ global [5]} \ $ global [5], \ $ g5, $ g5r, doSubOnRef {\ $ _} $ global [5]; ' –

+0

@Chris => la diferencia es que en mi ejemplo,' $ subs [5] 'es un cierre alrededor del alias, que también es (pero no tiene que ser) de un global. mi punto era que no estaba al tanto de ninguna otra forma de crear un cierre alrededor de un alias (usando solo el núcleo).en su ejemplo, no hay forma de devolver un cierre desde doSubOnRef, porque typeglob alias/locals solo se aplica a las variables del paquete, que no pueden (salvo lo anterior) cerrarse sobre –

+0

No es de peso ligero, pero se puede hacer con un léxico vinculado: @subs_ es equivalente a su paquete @subs: 'TiedScalarRef; requiere Tie :: Scalar; nuestro @ISA = qw (Tie :: StdScalar); sub FETCH {$ {$ _ [0] -> SUPER :: FETCH (@_)}} sub TIENDA {$ {$ _ [0] -> SUPER :: FETCH ($ _ [0])} = $ _ [ 1]} 'y' paquete principal; mi @subs_; por mi $ i (0..9) {mi $ r; tie $ r, 'TiedScalarRef', \ $ global [$ i]; push @subs_, sub {++ $ r}} $ subs_ [5](); ' –

4

Como han dicho otros, esto está documentado.

Mi entendimiento es que el comportamiento de aliasing @_, for, map y grep ofrece una velocidad y una optimización de la memoria, así como proporcionar interesantes posibilidades para la creatividad.Lo que sucede es esencialmente una invocación de referencia paso a paso del bloque del constructo. Esto ahorra tiempo y memoria al evitar la copia innecesaria de datos.

use strict; 
use warnings; 

use List::MoreUtils qw(apply); 

my @array = qw(cat dog horse kanagaroo); 

foo(@array); 


print join "\n", '', 'foo()', @array; 

my @mapped = map { s/oo/ee/g } @array; 

print join "\n", '', 'map-array', @array; 
print join "\n", '', 'map-mapped', @mapped; 

my @applied = apply { s/fee//g } @array; 

print join "\n", '', 'apply-array', @array; 
print join "\n", '', 'apply-applied', @applied; 


sub foo { 
    $_ .= 'foo' for @_; 
} 

Nota el uso de List::MoreUtilsapply función. Funciona como map pero hace una copia de la variable de tema, en lugar de usar una referencia. Si no te gusta escribir código como:

my @foo = map { my $f = $_; $f =~ s/foo/bar/ } @bar; 

te encantará apply, que la convierte en:

my @foo = apply { s/foo/bar/ } @bar; 

Algo a tener en cuenta: si pasa leer sólo los valores en una de estas construcciones que modifica sus valores de entrada, obtendrá un error "Modificación de un valor de solo lectura intentada".

perl -e '$_++ for "o"' 
0

Su $ a simplemente se usa como un alias para cada elemento de la lista mientras lo recorre. Se está utilizando en lugar de $ _. Puede decir que $ a no es una variable local porque está declarada fuera del bloque.

Es más obvio por qué la asignación de $ a cambia el contenido de la lista si lo considera un soporte para $ _ (que es lo que es). De hecho, $ _ no existe si defines tu propio iterador así.

foreach my $a (1..10) 
    print $_; # error 
} 

Si usted se pregunta cuál es el punto, consideremos el caso:

my @row = (1..10); 
my @col = (1..10); 

foreach (@row){ 
    print $_; 
    foreach(@col){ 
     print $_; 
    } 
} 

En este caso, es más fácil de leer para proporcionar un nombre amigable para $ _

foreach my $x (@row){ 
    print $x; 
    foreach my $y (@col){ 
     print $y; 
    } 
} 
0

Pruebe

foreach my $a (@_ = @ar) 

ahora mod ifying $ a no modifica @ar. funciona para mí en v5.20.2

Cuestiones relacionadas