This is my first question on SO, so sorry if it's silly, but it's something that really puzzled me when I recently came across it in production code. I've boiled my problem down to the two blocks of code, which I expected to do the same thing, namely produce a random number for each iteration:
for my $num (0 .. 5) {
my $id = int rand 10;
print "$id\n";
}
and
for (0 .. 5) {
my $tmp;
my $id = $tmp if $tmp;
$id = int rand 10 unless $id;
print "$id\n";
}
The first one does what I expect it to do, but the second one gives the same number for any number of iterations. $tmp
is always undefined in this simplification, so it's only there to show the behaviour, as leaving out = $tmp if $tmp
produces the result I'd expect.
I'd appreciate any insight into why this happens.
The reason for the strange behaviour is that you have made the declaration of $id
, as well as the assignment to it, conditional on the truth of $tmp
, which makes Perl throw a fit. perldoc perlsyn
has this to say about it
NOTE: The behaviour of a my, state, or our modified with a statement modifier conditional or loop construct (for example, my $x if ... ) is undefined. The value of the my variable may be undef, any previously assigned value, or possibly anything else. Don't rely on it. Future versions of perl might do something different from the version of perl you try it out on. Here be dragons.
You can demonstrate this for yourself if you change the code as follows, which works fine.
for (0 .. 5) {
my $tmp;
my $id;
$id = $tmp if $tmp;
$id = int rand 10 unless $id;
print "$id\n";
}
the behaviour of a statement modifier is undefined after a my ...
(see perlsyn). So don't use that...
You've stumbled across a bug that has long remained deliberately unfixed because it's useful. The line
my $id = $tmp if $tmp;
applies a statement modifier (the if
) to a variable declaration (the my
). Conditionally defining a variable doesn't make much sense, but because my
has both compile-time and run-time behavior this effectively creates a state variable: i.e. a variable that is lexically scoped to the enclosing block but which maintains its value between executions of that block.
The usual form for (deliberately) invoking the behavior you're seeing is
my $x if 0;
This behavior has been deprecated since Perl 5.10, which added state
variables to do this cleanly. Modern versions of Perl (5.10+) will emit the warning
Deprecated use of my() in false conditional
Even prior to version 5.10 using this was somewhat poor form as emulating state variables could be done cleanly (though not as succinctly) by adding an enclosing block and declaring the variable there. For example:
{
my $n = 0;
sub increment { return $n++; }
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With