in reply to Closures & aliases

It's due to the way closures are currently impelemented. Internally, sub 'test' has its own lexical $foo which just happens to be an alias to to the $foo in the main file (technically each CV has its own scrachpad, each with a pointer to the same SV). Within the for loop, the main $foo is now aliased to something else (technically the pointer for $foo in main's pad temporarily points elsewhere), so the two $foo's are now different.

I haven't been able to think of a way of fixing this that isn't inefficient, otherwise I would probably have done it by now.

Dave.

Replies are listed 'Best First'.
Re^2: Closures & aliases
by BrowserUk (Patriarch) on Jun 06, 2004 at 02:06 UTC

    Thanks for taking the time to explain it :)


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
Re^2: Closures & aliases
by ambrus (Abbot) on Jun 06, 2004 at 20:58 UTC

    Thanks for explaining.

    I thought that when perl localizes a variable (either with local or for) it just saves the old value of the variable somewhere (as an unnamed variable in the lexical evironment of the block in which you localize), and restores it on exit. (But it does the saving and restoring with an unwind-protect so that if an exception brings out perl from the block, the value gets restored.) I belive that implementation would not cause the faulty(?) behaiviour discussed here. I am still far from understanding perl internals, so that may be a bad solution (and slower).

    As you cannot localize a my variable in perl (which might have some relation to how local is implemented), I cannot reproduce that "bug" with local instead of for.

    Mit-scheme has the non-standard function (Update:) special form fluid-let, which is the equivalent of perl's local (let is that of my). With this, you can write

    (let* ((v 2) (f (lambda () v))) (fluid-let ((v 5)) (f)))
    This evaluates to 5, thus showing that f gets the new localized value of v. If you change fluid-let to let, you get 2 of course.

    Update: This is of course just localization, not a for loop, which may be quite different. Because of the reason mentioned above, you can not translate this code to perl as is. If you use a global variable:

    $v= 2; sub f {print $v} for $v (5) {f;}
    or
    $v= 2; sub f {print $v} {local $v= 5; f;}
    you get 5 as expected. It is possible that foreach is of a different nature than local in perl.
      The nub of the problem is that most people expect that in the following code,
      my $x = 1; sub foo { $x }
      There is a single variable called $x which is accessible from the main program and from within foo. The way it happens to be implemented internally is that they are treated as two separate variables that happen to be initially aliased to the same value. Here's a little ASCII diagram.

      Before the for loop; the two X's point to the same value

      $MAIN_X --- \ -> +---+ $FOO_X ------> | 1 | +---+
      and during for $x (9) { ...
      +---+ $MAIN_X -----> | 9 | +---+ +---+ $FOO_X ------> | 1 | +---+
      So calling foo from within the loop causes the 1 to be printed, not the 9.

      You are mostly right about how localization and for aliasing work, expect that its not entire values that are temporarily saved, it is simply a C pointer, either in the symbol table (eg local $x), in the scratchpad (eg my $x; for $x ()), or in an array or hash slot (eg local $h{foo}), that is pushed onto perl's savestack and replaced with a pointer to a different value.

      Dave.

        I see.

        So, with global variables, like in

        $a = 2; sub f { print $a; } { local $a = 5; f; }'
        or
        $a = 2; sub f { print $a; } for $a (5) { f; }
        we get 5 as the result, as perl localizes $a dynamically.

        However, if you put my before $a, perl can not localize the variable dynamically. In the first case, local raises an error, as Larry knows Perl can't localize a my, so he explicitly denied it. In the second case, however, if you put my before $a in the beginning, for will not carp (that would be a too bad restriction), but instead localizes $a in the wrong way.