In article <[EMAIL PROTECTED]>,
        Matthias Bauer <[EMAIL PROTECTED]> writes:
> Hi everybody,
> why is it that the following piece of code prints
> three _different_ strings (in Perl 5.8)?
> 
> ------8<------------------------
> sub bla() {
>   for my $i ( reverse 0 .. 5 ) {
>     $i = $i + 1;
>     print $i;
>   }
>   print "\n";
> }
> 
> bla();
> bla();
> bla();
> ------>8------------------------
> 
> It seems as if the array created to hold the
> returned AV of ''reverse 0..5`` is somehow
> re--used in later invocations of blah(). 
> Bug or feature or programming error?
> If it's a feature, it would be nice if
> ''use warn 'all';`` would warn about it :-)
> 
> Best regards,
> 

You obviously already know about this stuff (from using the term AV),
but for the other listeners (and because i hope to use this text
in other cases a simular issue comes up):

It's just the way perl works. If you do a=5 in a language like C,
you essentially put the integer 5 in piece of memory named a
   
a : 5

In perl it's like you have an extra level inbetween (a "scalar value", an SV)

   +---+ 
a: |   |-> 5
   +---+

Such SV's are expensive to make, so perl avoids making new ones as much
as possible, and prefers to just refer to the SV's multiple times (they 
are reference counted). This principle explains much of perls weirder
aliasing, wether it's documented or not:

So if you do 

 for my $a (@array) {
 }

and @array is:

       +--+
array: |  +---+-----+-----+-----+
       +--+   |     |     |     |
            +-+-+ +-+-+ +-+-+ +-+-+ 
            |   | |   | |   | |   |
            +-+-+ +-+-+ +-+-+ +-+-+ 
              |     |     |     |
              V     V     V     V
              0     1     2     3
$a is aliassed with each of these in turn, e.g. the first time:

       +--+
array: |  +---+-----+-----+-----+
       +--+   |     |     |     |
            +-+-+ +-+-+ +-+-+ +-+-+ 
         a: |   | |   | |   | |   |
            +-+-+ +-+-+ +-+-+ +-+-+ 
              |     |     |     |
              V     V     V     V
              0     1     2     3

So now doing $a=5 makes this into:

       +--+
array: |  +---+-----+-----+-----+
       +--+   |     |     |     |
            +-+-+ +-+-+ +-+-+ +-+-+ 
         a: |   | |   | |   | |   |
            +-+-+ +-+-+ +-+-+ +-+-+ 
              |     |     |     |
              V     V     V     V
              5     1     2     3

So changing the loop variable changes the array, as most people know.
Now you see WHY.

But it's more general. E.g. if you do

      $x = 2; print $x+ ++$x;

Setting $x to 2:
    +---+
x : |   |->2
    +---+

Next is $x + (++$x), which first looks at the left of "+", which is just
$x itself:
    
                   +---+
x :                |   |->2
left side of "+":  +---+

Now it does ++$x

                   +---+
x :                |   |->3
left side of "+":  +---+

next it takes the result (which is the new $x) as the right side of +:

                   +---+
x :                |   |->3
left side of "+":  +---+
right side of "+":

And then does the addition, giving "6" (and NOT 5)

Or in your case: for (reverse 0..3) { $_++ }

It first constructs the argument array for reverse:
This is actually done at compile time !

       +--+
array: |  +---+-----+-----+-----+    (this is an array value, an AV)
       +--+   |     |     |     |
            +-+-+ +-+-+ +-+-+ +-+-+ 
            |   | |   | |   | |   |
            +-+-+ +-+-+ +-+-+ +-+-+ 
              |     |     |     |
              V     V     V     V
              0     1     2     3

Then reverse constructs a new result array, and reuses the element SV's:

       +--+
array: |  +---+-----+-----+---------+
       +--+   |     |     |         |
            +-+-+ +-+-+ +-+-+     +-+-+ 
            |   | |   | |   |  _: |   |
            +-+-+ +-+-+ +-+-+     +-+-+ 
              |\    |\    |\        |\
              | \   | \   | \       | \  +--+
              |  +- | -+- | -+----- | -+-|  | : result array
              |     |     |         |    +--+   (last shown is really
              V     V     V         V            first element)
              0     1     2         3

Shown here is $_ aliased on the first element of the reversed list
so if you now do $_++, you get:

       +--+
array: |  +---+-----+-----+---------+
       +--+   |     |     |         |
            +-+-+ +-+-+ +-+-+     +-+-+ 
            |   | |   | |   |  _: |   |
            +-+-+ +-+-+ +-+-+     +-+-+ 
              |\    |\    |\        |\
              | \   | \   | \       | \  +--+
              |  +- | -+- | -+----- | -+-|  | : result array
              |     |     |         |    +--+   (last shown is really
              V     V     V         V            first element)
              0     1     2         4

so in the end you end up with:
       +--+
array: |  +---+-----+-----+-----+
       +--+   |     |     |     |
            +-+-+ +-+-+ +-+-+ +-+-+ 
            |   | |   | |   | |   |
            +-+-+ +-+-+ +-+-+ +-+-+ 
              |\    |\    |\    |\
              | \   | \   | \   | \  +--+
              |  +- | -+- | -+- | -+-|  | : result array
              |     |     |     |    +--+   (last shown is really
              V     V     V     V            first element)
              1     2     3     4

The statement finishes and only the compile time array is left:

       +--+
array: |  +---+-----+-----+-----+
       +--+   |     |     |     |
            +-+-+ +-+-+ +-+-+ +-+-+ 
            |   | |   | |   | |   |
            +-+-+ +-+-+ +-+-+ +-+-+ 
              |     |     |     |
              V     V     V     V
              1     2     3     4

And the next execution works on 1..4 effectively.

So it's actually not the reverse 0..3 AV (array value) 
that gets reused, but the 0..3 itself.

(I was actually a bit surprised. I thought perl would 
also already do the reverse at compile time, but Deparse
says it doesn't).

Here are other fun examples:

------------------------------

perl -wle 'print map $_++, 1..3 for 1..3'
123
234
345

------------------------------

perl -wle 'my $k; $_=$k++ for sort @a=qw(a e c d b); print "@a"'
0 4 2 3 1

This is an easy way of generating the mapping array for a sort. e.g. 
the second element, "e" will end up in place 4 after sort, the second 
element of the result (don't rely on this on big array before 5.8)

------------------------------

perl -wle 'sub fun {
    my $str = shift;
    print "$str: ", ($str & "+0") eq "0" ? "numeric" : "string"
}
fun("waf"); fun(0); fun("waf")'

waf: string
0: numeric
Argument "waf" isn't numeric in bitwise and (&) at -e line 1.
waf: numeric

This one tries to use a dirty trick to see if an argument was a number
or a string. It fails because the compile time constant "+0" actually 
becomes a number, so the function starts failing after the first numeric
call.

Reply via email to