Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl scalar declared within the scope of a for loop retains its value between iterations

Tags:

for-loop

perl

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.

like image 525
flesk Avatar asked Jan 07 '13 14:01

flesk


3 Answers

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";
}
like image 104
Borodin Avatar answered Nov 03 '22 00:11

Borodin


the behaviour of a statement modifier is undefined after a my ... (see perlsyn). So don't use that...

like image 25
pavel Avatar answered Nov 03 '22 00:11

pavel


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++; }
}
like image 28
Michael Carman Avatar answered Nov 02 '22 23:11

Michael Carman