Re: "$_" vs. $_
by bart (Canon) on Apr 07, 2007 at 18:16 UTC
|
Your problem is that you didn't localize $_ in the sub. As a result, the $_ from the outside world is changed. And since parameters are passed by reference, your parameter is changed after you passed it in. "$_" passes a copy, so that one doesn't change.
As a fix, which IMO is much less interesting than the phenomenon we just witnessed,
you can localize the change by
sub foo
{
while (@_) { local $_ = shift; $params->{$_} = shift; }
print "$_: $params->{$_}\n", for keys %$params;
}
or
sub foo
{
local $_;
while (@_) { $_ = shift; $params->{$_} = shift; }
print "$_: $params->{$_}\n", for keys %$params;
}
| [reply] [d/l] [select] |
|
The real WTF here is that
while (@_) { local $_ = shift; $params->{$_} = shift; }
is used instead of
my %params = @_;
| [reply] [d/l] [select] |
|
Now that you pointed out what is, in retrospect, the obvious, it's easier to see when you do an identical thing:
my $bar = "world";
sub foo
{
while (@_) { $bar = shift; $params->{$bar} = shift; }
print "$_: $params->{$_}\n", for keys %$params;
}
foo ( hello => $bar );
We're used to seeing things like this because we declare the variables and make that mental connection to it as "global variable", which you can see getting changed inside foo(). For some reason, that doesn't strike me as so obvious when using $_, since I sort of think of it more as an abstract placeholder for short-term uses of values when I don't want to make a whole new variable.
anyway, thanks for the clarification
| [reply] [d/l] |
|
Now that you pointed out what is, in retrospect, the obvious, it's easier to see when you do an identical thing:my $bar = "world";
sub foo
{
while (@_) { $bar = shift; $params->{$bar} = shift; }
It's not, strictly speaking, the same thing: the big difference being that $_ is a package variable and your $bar above, a lexical one.
It is perhaps interesting in this respect to notice that a lexical $_ is provided in blead; to quote from there:
Lexical $_
The default variable $_ can now be lexicalized, by declaring it like any other lexical variable, with a simple
my $_;
The operations that default on $_ will use the lexically-scoped version of $_ when it exists, instead of the global $_.
In a map or a grep block, if $_ was previously my'ed, then the $_ inside the block is lexical as well (and scoped to the block).
In a scope where $_ has been lexicalized, you can still have access to the global version of $_ by using $::_, or, more simply, by overriding the lexical declaration with our $_.
Back to stable, you've been repeatedly pointed to local, in which case it's also worth pointing out that experienced Perl hackers often told me that
local $_=whatever;
can break because of a bug with Perl tied variables that may bite you in the neck first or later:
#!/usr/bin/perl -l
use strict;
use warnings;
use Tie::Array;
my @q = my @q0 = qw/foo bar/;
sub foo {
local $_ = 'baz';
print "@q $_";
}
foo for @q;
tie @q, 'Tie::StdArray';
@q=@q0;
print '-' x 11;
foo for @q;
__END__
(Learnt in clpmisc and probably adapted from an example posted there by someone else.)
| [reply] [d/l] [select] |
Re: "$_" vs. $_
by talexb (Chancellor) on Apr 08, 2007 at 03:34 UTC
|
In my opinion, $_ springs into being when Perl feels it's appropriate -- it's an auto-vivified rvalue.
On the other hand, $_ is *never* an lvalue unless you're using a regexp to modify the value, as in $_ =~ s/foo/bar/;, and even that's stretching a point, because of course uou can do the same thing with just s/foo/bar/;
So I would say that any code that *sets* $_ is, by definition, wrong.
Alex / talexb / Toronto
"Groklaw is the open-source mentality applied to legal research" ~ Linus Torvalds
| [reply] [d/l] [select] |
|
| [reply] |
|
my @a = map { s/\s*(#.*)?$//g; $_ } <DATA>;
my @b;
while (my $group = shift @a) {
if ($group =~ m/\D(\d+)-(\d+)(?:\D|$)/) {
my $range = "$1-$2";
foreach my $i ($1..$2) {
(my $addr = $group) =~ s/(\D)$range(\D|$)/$1$i$2/;
push @a, $addr;
}
} else {
push @b, $group;
}
}
print join("\n", @b);
There are cases where using $_ helps, but I don't think this is one of them.
Note: The localization of $1 and $2 is unnecessary. It's already being done by the for.
Note: local $_ doesn't preserve $_ in all situations, unfortunately. for ($var) is a far better way of aliasing $_.
| [reply] [d/l] [select] |
|
|
A reply falls below the community's threshold of quality. You may see it by logging in.
|
|
So I would say that any code that *sets* $_ is, by definition, wrong.
Unnecessary, perhaps, but not wrong. Setting $_ can be useful:- As a topicalizer in a single-item for loop, to make a series of operations on a common value read more cleanly.
Instead of this:
$h->{foo}{bar} =~ s/[._]/ /g;
Some::Custom::Function($h->{foo}{bar});
$h->{foo}{bar} =~ s/^\s+//;
$h->{foo}{bar} =~ s/\s+$//;
$h->{foo}{bar} =~ s/`/'/g;
You can write this:
for ($h->{foo}{bar}) {
s/[._]/ /g;
Some::Custom::Function($_);
s/^\s+//;
s/\s+$//;
s/`/'/g;
};
-
And the occasional weird circumstance, like conveniently modifying the values of a hash:
map { $_ = foo( $bar, $_ ) } values %somehash;
On the other hand, where real for loops are concerned, I find it reduces confusion to always provide your own lexical topic (e.g. for my $key (@keys) { ... } ). A bit backwards from the way the language is designed, I suppose.
| [reply] [d/l] [select] |
|
| [reply] |
|
Re: "$_" vs. $_
by Brovnik (Hermit) on Apr 11, 2007 at 11:44 UTC
|
I think this is what you wanted :
#!/usr/bin/perl
use strict;
use warnings;
sub foo
{
my %params = @_;
print "$_: $params{$_}\n", for keys %params;
}
my $word = shift @ARGV || "world";
foo ( hello => $word );
Do not use $_ as a temporary variable unless essential.
Declaring and naming the variable catches a lot of issues and solves (for example) the problem you came across as well as improving readability.
| [reply] [d/l] |
implicit local $_ happens sometimes
by doom (Deacon) on Apr 11, 2007 at 19:12 UTC
|
I think that one of the things that's mildly confusing in this area is that perl does an implicit "local" for you
whenever it sets $_ for you (in foreach, map and grep loops), but if you do the setting of $_ yourself, then you've got a new problem you don't see otherwise.
(I still don't understand why our "standard practice" doesn't include doing a "local $_" at the beginning of every sub... but then on the other hand, I guess it isn't all that common to get burned by $_ problems.)
Anyway, here's a script that demos what I'm talking about:
use warnings;
use strict;
use Test::More qw(no_plan);
# we're going to try doing various things to $_,
# and we want to see if it changes the initial value
my $initial_value = 'Some Value';
my @initial_array = qw( wun tew thuree foah fahv sex sevhun );
{#1 map perlfunc: "locally setting $_ to each element"?
my $testcase = "map";
my @array = @initial_array;
$_ = $initial_value;
my @whateva = map{ s/^f/F/ } @array;
is( $_, $initial_value, $testcase);
}
{#2 foreach
my $testcase = "foreach";
my @array = @initial_array;
$_ = $initial_value;
my @whateva = ();
foreach (@array) { s/^f/F/; push @whateva, $_ };
is( $_, $initial_value, $testcase);
}
{#3 while - this one fails
my $testcase = "while";
my @array = @initial_array;
$_ = $initial_value;
my @whateva = ();
while (@array) { $_ = pop @array; s/^f/F/; push @whateva, $_};
is( $_, $initial_value, $testcase);
}
{#4 while with local
my $testcase = "while with explicit local";
my @array = @initial_array;
$_ = $initial_value;
my @whateva = ();
while (@array) { local $_ = pop @array; s/^f/F/; push @whateva, $_};
is( $_, $initial_value, $testcase);
}
{#5 grep
my $testcase = "grep";
my @array = @initial_array;
$_ = $initial_value;
my @whateva = grep{ m/^f/ } @array;
is( $_, $initial_value, $testcase);
}
| [reply] [d/l] |
|
| [reply] [d/l] |