I thought I understood map however the following has results that I don't understand. I know why it's happening I just don't know how it is happening.
The problem is that the contents of @array are changing because $_
is being reset during _do_stuff_to_file
call. so what is printed is here: \nhere:\n
when I expect it to be here: donkie\nhere: kong\n
.
Note: This is not tested code. It's just what I remember seeing from lab. Why are the contents of @array
changing?
If I set $_
to $f
before returning 1 from _some_func
. Then the array is still intact.
Here is an example program to illustrate what I am seeing:
my @array = ("donkie", "kong");
map { push @junk, _some_func('blah', $_); } @array;
if (join ('', @junk) !~ /0/)
{ # for example sake this is always true since return 1 from _some_func.
print map { "here: $_\n"; } @array;
}
sub _some_func
{ # for example sake, lets say $f always exists as a file.
my $j = shift;
my $f = shift;
return 0 if !open(FILE, "< $f");
close FILE;
_do_stuff_to_file($f);
return 1;
}
sub _do_stuff_to_file
{
my $f = shift;
open(IN, "< $f");
open(OUT, "> $f.new");
开发者_如何转开发 while (<IN>)
{
print OUT;
}
close IN;
close OUT;
}
Many functions in Perl use the default variable $_
. Among these are map
and the readline operator <>
. Like foreach
, map
makes the loop variable an alias for each element of the list it processes. What's happening is that this line:
while (<IN>)
is assigning to $_
while the aliasing of the map
is in effect. This is one of the problems with using $_
(or any other global variable) -- strange action at a distance. If you're going to use $_
, localize it first:
local $_;
while (<IN>)
...
Alternately, use a lexical variable instead:
while (my $line = <IN>)
Most things that set $_ implicitly alias it, so won't cause this problem; the exception is while (<filehandle>)
. While you can localize $_ (ideally with my $_;
), it's better just to never let while implicitly set $_. Do while ( my $line = <filehandle> )
instead. (The special implicit defined() still happens.)
modifying $_ will change your initial array, because $_ is an alias to current element. Your code should look like this:
my @array = ("donkie", "kong");
my @junk=map {_some_func('blah', $_) } @array;
if (join ('', @junk) !~ /0/)
{ # for example sake this is always true since return 1 from _some_func.
print map { "here: $_\n"; } @array;
}
sub _some_func
{ # for example sake, lets say $f always exists as a file.
my $j = shift;
my $f = shift;
return 0 if !-e $f;
_do_stuff_to_file($f);
return 1;
}
sub _do_stuff_to_file
{
my $f = shift;
local $_;
open(IN, "<",$f);
open(OUT, ">", "$f.new");
while (<IN>)
{
print OUT;
}
close IN;
close OUT;
}
P.S. map returns array with same number of elements (if scalar is returned from block). grep returns only elements for which block is true.
I approve the answers from Alexander and Michael: _do_stuff_to_file()
is changing the value of $_
.
As in the context of map
$_
is just a name for the storage of the mapped element, the array is changed.
Alexander and Michael propose to change _do_stuff_to_file()
so they do not affect the $_
value. This is good practice to local-ize special variables such as $_
to avoid perturbing the outside scope.
Here is an alternative solution that avoid touching that function: "break" the link inside the map block by local-izing before calling the function:
map { my $x=$_; local $_; push @junk, _some_func('blah', $x); } @array;
or more following the common style:
@junk = map { my $x=$_; local $_; _some_func('blah', $x) } @array;
精彩评论