jh has asked for the wisdom of the Perl Monks concerning the following question:

I am using perl v5.8.3 for Solaris.

If I were to write a loop like this:

1 for ( 1 .. 3 ) { 2 my $x = $_; 3 } 4 print $x;

I would except the following to happen:

1. Each time through the loop, the lexical variable $x is declared and set to $_ (1, then 2, then 3).

2. Outside the loop, $x has never been defined, so nothing is printed.

This is what happens.

I would further expect that if I were to "use strict", the code would break at compile-time on line 4 because $x is not predeclared at the top level of lexical scope.

Again, this is what happens.

But weird stuff starts happening if I use a postfix loop:

1 my $x = $_ for ( 1 .. 3 ); 2 print $x;

This code is conceptually identical to the first example, and Deparse returns identical output.

The print statement still prints nothing, so the "my" declaration is not making a top-level lexical (i.e. a lexical variable at the top level of the package). The first $x is still restricted in scope to the body of the for-loop.

However, a "using strict" no longer catches the undeclared $x outside of the loop.

This is my guess as to what is happening: "use strict" checks for scope based on brackets, before anything is compiled; as a result the "my" statement on line 1 APPEARS to "use strict" be a top-level lexical variable to; therefore the use of the undefined $x on line 2 is not flagged by "use strict"

When the code is compiled, however, and the actual lexical rules are realized, a postfix for-loop is compiled just like a regular prefix for-loop; i.e. the code compiles like the first example. Then, the loop executes and the print statement, having no $x in scope, just creates it, set it to undef, and prints it. This is not an error because "use strict" (at least the 'vars' part of it) only is checked at compile time, so once the compile has passed, the code executes as if "use strict" had never been turned on, and as we all know from that time we forgot to turn on "use strict", having a brand new variable pop into existence is no problem at all!

Is this conclusion correct?

This would seem to be a bug in Perl. If the "my" declaration on line 1 of the second example is a top-level lexical, then it should still be defined once you get out of the loop, as if you had done:
1 my $x; 2 $x = $_ for ( 1 .. 3 ); 3 print $x;
But this is not what happens, so now I must assume that $x is lexical to the body of the for-loop. Thus, I would expect "use strict" to flag the use of $x in the print statement, but this doesn't happen either.

Is this a known bug in Perl?

Note that if you explicitly put brackets around the body of the postfix for-loop, as in:

1 do { my $x = $_ } for ( 1 .. 3 ); 2 print $x;
then everything is happy: "use strict" DOES now catch the undefined $x on line 2.

And now for some REAL weirdness...

Suppose I explicitly predeclared my top-level lexical:

1 my $x = 6; 2 my $x = $_ for ( 1 .. 3 ); 3 print $x;
This doesn't work at first blush because "use warnings" complains of a duplicate declaration. If you take out "use warnings", you get really bizarre behavior.

Based on my conclusion above, the first $x should be a top- level lexical, and the second $x should be a lexical with scope of the loop body, even if "use strict" doesn't think so, but the for-loop clobbers the top-level variable! it doesn't leave it set at 3, as it would if you took the "my" out of line 2; it doesn't avoid it as if you used a prefix loop, or reset it back to 6, as it would if you could substitute a "local" for the "my" on line 2.

It resets $x back to undef! What is going on NOW?

Replies are listed 'Best First'.
Re: Lexical scope vs. postfix loops (perl bug?)
by Tanktalus (Canon) on Aug 25, 2008 at 20:00 UTC

    Using statement modifiers on a my declaration results in undefined behaviour. So the answer is "don't do that" ;-) (see perlsyn)

    "Undefined" means, as always, that anything can happen. The "do { my $x = ... } for (1 .. 3)" version is not such a case (the modifier is on do, not my), so that's why it behaves reasonably.

      I see!

      Do you know why a specific behavior is not defined? Just curious.

      I also note that the warning appears in the perlsyn page 5.10, but not in my perlsyn page (5.8). Is this something that bit people until they added it to the manual? I didn't see it in the camel either...

      Anyway, thanks for the help.

        Do you know why a specific behavior is not defined? Just curious.

        The behaviour is actually quite predictable, but it's probably not one the Perl developers want to be held to. If that's the case, the warning in the documentation serves to avoid a dependancy on the current behaviour.

        In case you're curious,

        The current compile-time behaviour of my is to define the variable for the rest of the scope, starting with the next statement.

        The current run-time behaviour of my is to place an instruction on the stack to clear (if refcount = 0) or replace (if refcount > 0) the variable on scope exit. It also returns the variable as an lvalue.

        Do you know why a specific behavior is not defined?
        I think that there's no one consistent way to define one. Usually variables are block scoped, so making my $x for @list behaving the same as do { my $x } for @list is just weird.

        One could argue that $stuff for @list should always be the same as for @list { $stuff }, but then you could write

        use strict; print $x*$x for my $x (0..10);

        Which seems equally weird, because a variable is used (textually)before it is declared. (And I don't know if that's technically possible in the perl compiler).

        So regardless from which angle you look at it, it smells badly. So the behaviour is not defined. Maybe someday a hero of programming languages will find something that's consistent in every way, and then it can be still implemented.