in reply to How do closures and variable scope (my,our,local) interact in perl?

Declaring a variable with our effectively creates a package variable (i.e. a typeglob entry) and bequeaths a lexical scope upon the short name symbol. A for() loop aliases it's loop variable, so that our $name loop-variable behaves like a local. That's why declaring $name with our results in $name being undefined after the for() loop run, hence the generated subroutines output an empty $name.

Why does the my-variable version work as expected? - Because the for() loop aliases the my variable as - a my variable. Now, for those the compiler inserts an opcode which clears it after (or before?) every iteration (on ENTER or LEAVE), so that you have a brand new $name (with its own private storage!) each time through the loop.

If you close over a localized our() variable, as per your example, all your generated subs share the same local, which isn't visible outside.

Perl is trying really hard to dwim here, and imho does a good job ;-)

update:

Actually, there's more to it, since the loop variable gets an alias of the list it iterates over. Consider:

my $name; for $name (qw(red blue green yellow orange purple violet)) { no strict 'refs'; # allow symbol table manipulation no warnings 'redefine'; #ignore noise about redefinitions *$name = sub { my $sOutput = "<FONT COLOR='$name'>@_</FONT>"; $name = 'something wacky and wonderful'; #change the value return $sOutput; }; } print "red: ${\(red())}\n"; __END__ Modification of a read-only value attempted at - line 7.

Here the for() iterates over a list of literals, and those are read-only. But! ...

our $name; for $name (qw(red blue green yellow orange purple violet)) { no strict 'refs'; # allow symbol table manipulation no warnings 'redefine'; #ignore noise about redefinitions *$name = sub { my $sOutput = "<FONT COLOR='$name'>@_</FONT>"; $name = 'something wacky and wonderful'; #change the value return $sOutput; }; } print "red: ${\(red())}\n"; __END__ red: <FONT COLOR=''></FONT>

...why? In this case, the sub closes over the localized $name, which is undefined after the loop finished. That's why you get

----- sub used inside loop that defined it ------ trying out something wacky and wonderful: <FONT COLOR='something wacky + and wonderful'></FONT>

inside the last loop - you had modified $colors[0] in the previous loop.

Replies are listed 'Best First'.
Re^2: How do closures and variable scope (my,our,local) interact in perl?
by ELISHEVA (Prior) on Jun 16, 2009 at 17:09 UTC

    Wow. @colors was modified. Didn't think of that. That explains a lot. So it all boils down to a subtle difference between localizing and aliasing:

    • our $name. Inside a foreach loop, the subroutine closes over the $main::name. It gets the localized value whenever $main::name is localized and the global value when not, just as does $main::name. Any assignment to $main::name within the closure changes the global variable at whatever localization level it happens to be running in.
    • my $name;. Inside the foreach loop, the subroutine closes over whatever $name happens to be aliased to, in this case, $colors[0] when $name eq 'red' and $colors[-1] when $name eq 'violet'. No matter where the subroutine runs, any assignment to $name within the closure changes the thing aliased, i.e. an element of @colors

    In the above quote from perlsyn it says both my and our are localized and doesn't make a distinction between aliasing and localizing. In your opinion is this a documentation bug? a perl bug? or neither?

    Thanks, beth

    Update: LanX's comment below is helpful here. He points out that lexical variables (i.e. my $name) can't be localized, so temporary aliasing is a way of faking it. Inside the loop itself, temporary aliasing is pretty much indistinguishable from localizing, but the differences between the two (localization and aliasing) become much more noticable if the variable is captured by a closure.

      t gets the localized value whenever $main::name is localized and the global value when not

      No. When $name is executed, it gets the current value of $main::name. There's nothing conditional about it.

      Any assignment to $main::name within the closure changes the global variable at whatever localization level it happens to be running in.

      Again, it simply changes the variable $main::name. There's nothing conditional about it. Localisation just means a backed-up value will be assigned to the variable later.

      Inside the foreach loop, the subroutine closes over whatever $name happens to be aliased to,

      It captures the variable, whether it's an alias or not.

      No matter where the subroutine runs, any assignment to $name within the closure changes the thing aliased, i.e. an element of @colors

      If the captured variable is an alias, yes. That's what an alias is. It's got nothing to do with captures.

      In the above quote from perlsyn it says both my and our are localized and doesn't make a distinction between aliasing and localizing. In your opinion is this a documentation bug? a perl bug? or neither?

      They're independent.

      • "for my $x" creates(*) $x and aliases it.
      • "my $x; for $x" localises $x and aliases it.
      • "for our $x" localises $x and aliases it.

      I don't see anything wrong in perlsyn. Which statement is giving you pause?

      * — In practice, my vars aren't actually created at declaration and destroyed at scope exit, but that's how they're specified to behave. In reality, it might actually simply be a localisation in this case.

        Perhaps we are just going in word circles here, but it seems to me that an enclosed global does in fact get whatever happens to be the localized value of its current context. In this example below, the sub is defined in the outer block, but when it runs in a block where $x is localized, it uses the localized value, not the value from the outer block.

        prints out

        ----- our $x; for $x ...; after for block --------- setting $x to 'x' doubling $x: <x|x> localized $x to 'X' doubling $x: <X|X> leaving localization block doubling $x: <x|x|x|x>

        But maybe what you are trying to say is that the way I talked about "localization" indicates a misunderstanding? I was thinking that localization "overlay" the variable with a new variable, but it seems that you are saying that it doesn't do that at all: rather it shoves the current value off into a side area, sets a new value, and the copies the old value back at the end of the block. Do I have that right?

        As for the troubling difference, I suppose I think of file scope my variables as the "same kind" of variable as "our" variables, just private to a file. My guess is that you do not - for one - there are some major differences: our variables can be localized; my variables, even file scope my variables, cannot.

        All the same, outside of the for loop I expect anything associated with file scope $x to have the value assigned to file scope $x. But that is not what happens. Consider what happens if we use my $x; for $x.... In this case, running the sub after the for block is not affected at *all* by the current value of $x, even though $x was declared as a file scope lexical. It does, however, make and pick up changes to @colors as this code and output shows:

        outputs

        ----- my $x; for $x ... --------- set $x to 'x' $colors[0]=<red> $x=<x> doubling $x: <red|red> $colors[0]=<red|red> $x=<x> setting $colors[0] to 'RED' doubling $x: <RED|RED> doubling $x: <RED|RED|RED|RED>

        In other words, when captured by a closure, the our variable loses its aliased association outside of the for loop, the file scope my variable does not [ inside the closure ].

        Best, beth

        Update: clarification in square brackets. As ikegami point out below, my $x not in the closure does in fact lose its aliasing.

      I'd say neither, since

      qwurx [shmem] ~ > perl -le 'local my $foo' Can't localize lexical variable $foo at -e line 1.

      localizing in that context doesn't mean a local opcode is involved; rather, a local instance of whatever thing the loop variable is will be allocated. So localizing means that, for the loop variable, but aliasing is what happens to the current element of the list iterated over. Your code is a fine example for my/local, space/time (was: Re: The difference between my and local). The aliasing happens no matter what scoping rules apply to the loop variable:

      our $name; for $name (qw(red blue green yellow orange purple violet)) { $name = "foo"; } __END__ Modification of a read-only value attempted at - line 3.

      But since an our localized variable works in time, at calling time $name is just the localized instance of $main::name, and the list for which it was used to iterate over has gone.