Reference to loop index in a closure

B

Bill Smith

My "Case 1" creates an array of references to anonymous subroutines.
Each subroutine is intended to print the value of the loop index ($i)
when it (the subroutine) was created. One of the subroutines is
executed to demonstrate that the wrong value is printed. (The index is
never 3)

"Case 2" is an experimental version which solves the problem by using
another variable ($j) as the loop index
and making $i a lexical variable within the scope of the loop.

use strict;
use warnings;

my @ref1;

for (my $i = 0; $i < 3; $i++){
$ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
}
&{$ref1[1]};


for (my $j = 0; $j < 3; $j++){
my $i = $j;
$ref1[$i] = sub{print "Case 2 = $i OK\n"};
}
&{$ref1[1]};
__END__

What is wrong with "Case 1"? I first encountered the problem in a Tk
application and wrote this case as a aide to solving it. The successful
"Case 2" was found by trial and error. Both appear to conform to
section of "Programming Perl" on closures.

Thanks,
Bill
 
A

A. Sinan Unur

My "Case 1" creates an array of references to anonymous subroutines.
Each subroutine is intended to print the value of the loop index ($i)
when it (the subroutine) was created. One of the subroutines is
executed to demonstrate that the wrong value is printed. (The index is
never 3)

I am going to rearrange your post a little bit. Here is your case 1:
use strict;
use warnings;

my @ref1;

for (my $i = 0; $i < 3; $i++){
$ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
}
&{$ref1[1]};

I do not understand why you would ever expect the index to be 3. OTOH, when
I ran your script unmodified on my system, it produced the following
output:

C:\Home\asu1> perl -v

This is perl, v5.8.3 built for MSWin32-x86-multi-thread
(with 8 registered patches, see perl -V for more detail)

....

C:\Home\asu1> tttt
Case 1 = 3 WRONG

This puzzled me as well. Lacking sufficient knowledge, I am unsure how to
explain this, but I would have done the following:

use strict;
use warnings;

my @ref1;

$ref1[$_] = index_gen($_) for (0 .. 2);

$_->() for @ref1;

sub index_gen {
my $index = shift;
return sub { print "$index\n"; }
}

__END__

Which generates the output (in line with my expectations):

C:\Home\asu1> tttt
0
1
2
 
B

Ben Morrow

Quoth "A. Sinan Unur said:
My "Case 1" creates an array of references to anonymous subroutines.
Each subroutine is intended to print the value of the loop index ($i)
when it (the subroutine) was created. One of the subroutines is
executed to demonstrate that the wrong value is printed. (The index is
never 3)

I am going to rearrange your post a little bit. Here is your case 1:
use strict;
use warnings;

my @ref1;

for (my $i = 0; $i < 3; $i++){
$ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
}
&{$ref1[1]};

I do not understand why you would ever expect the index to be 3. OTOH, when
I ran your script unmodified on my system, it produced the following
output:

C:\Home\asu1> perl -v

This is perl, v5.8.3 built for MSWin32-x86-multi-thread
(with 8 registered patches, see perl -V for more detail)

...

C:\Home\asu1> tttt
Case 1 = 3 WRONG

This puzzled me as well.

The variable $i is only declared once (the first section of a C-style
loop is only executed once); hence there is only one variable. All the
subs refer to the same variable $i, so whatever you do they will always
print the same thing.

The loop test is '$i < 3'; the loop will continue until that is false.
At that point, $i == 3, and remains so for the rest of the program. That
is why the subs will all print 3.

The following may help:

my @subs;
for (my $i = 0; $i < 3; $i++) {
push @subs, sub {
print "BEFORE: \$i is now $i\n";
$i++;
print "AFTER: \$i is now $i\n";
};
}

$_->() for @subs;
use strict;
use warnings;

my @ref1;

$ref1[$_] = index_gen($_) for (0 .. 2);

$_->() for @ref1;

sub index_gen {
my $index = shift;

This line (and the 'my $i = $j' in the OP's solution) is crucial: it
creates a new variable each time through the loop, so each sub gets a
different variable in its lexical pad. This is (usually) what you want.

Note that a Perl-style loop like

for my $i (0..2) {

will also create a new copy of $i each time through, unlike the C-style
which only has one copy. This is yet another reason to prefer Perl-style
for loops :).

Ben
 
A

A. Sinan Unur

Quoth "A. Sinan Unur said:
news:F7Ouc.34403$DC1.6921175 @news4.srv.hcvlny.cv.net:
....
use strict;
use warnings;

my @ref1;

for (my $i = 0; $i < 3; $i++){
$ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
}
&{$ref1[1]};
....
C:\Home\asu1> tttt
Case 1 = 3 WRONG

This puzzled me as well.

The variable $i is only declared once (the first section of a C-style
loop is only executed once); hence there is only one variable. All the
subs refer to the same variable $i, so whatever you do they will
always print the same thing.

A-ha! Thank you very much for the explanation.
 
G

gnari

for (my $i = 0; $i < 3; $i++){
$ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
}
&{$ref1[1]};

i am no internal expert, but my perl sense tells me
that when the index is made lexical here, it is equivalent to



my $i;
for ($i = 0; $i < 3; $i++){
$ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
}
}

thus only one $i variable for the whole loop

for (my $j = 0; $j < 3; $j++){
my $i = $j;
$ref1[$i] = sub{print "Case 2 = $i OK\n"};
}

here a separate $i is instantiated for each round

gnari
 
B

Ben Morrow

Quoth "gnari said:
for (my $i = 0; $i < 3; $i++){
$ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
}
&{$ref1[1]};

i am no internal expert, but my perl sense tells me
that when the index is made lexical here, it is equivalent to



my $i;
for ($i = 0; $i < 3; $i++){
$ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
}
}

thus only one $i variable for the whole loop

To be exact, it's equivalent to:

{
my $i = 0;
while ($i < 3) {
...
}
continue {
$i++;
}
}

:)

Ben
 
U

Uri Guttman

BS> My "Case 1" creates an array of references to anonymous subroutines.
BS> Each subroutine is intended to print the value of the loop index ($i)
BS> when it (the subroutine) was created. One of the subroutines is
BS> executed to demonstrate that the wrong value is printed. (The index is
BS> never 3)

BS> "Case 2" is an experimental version which solves the problem by using
BS> another variable ($j) as the loop index
BS> and making $i a lexical variable within the scope of the loop.

BS> my @ref1;

BS> for (my $i = 0; $i < 3; $i++){
BS> $ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
BS> }
BS> &{$ref1[1]};

the $i in the loop and closure are the SAME variable. so $i will end up
at 3 as it has to fail the loop. all of the closures will print three in
this case (try it) as they share the same variable as well.

BS> for (my $j = 0; $j < 3; $j++){
BS> my $i = $j;
BS> $ref1[$i] = sub{print "Case 2 = $i OK\n"};
BS> }


you copied the value before it hit 3 but even so, all the closures will
print 2. you never printed the closure earlier in the list and haven't
seen that bug yet.

BS> What is wrong with "Case 1"? I first encountered the problem in a Tk
BS> application and wrote this case as a aide to solving it. The successful
BS> "Case 2" was found by trial and error. Both appear to conform to
BS> section of "Programming Perl" on closures.

case 2 is not successful, you just didn't test it enough.

the variable being closed upon is visible to all closures created where
that variable is in scope. ATM, i can't think of a simple way to do what
you want. perhaps you have an XY problem and think you need to solve it
this way but really don't? what is the original problem?

uri
 
B

Bill Smith

--snip background info.
use strict;
use warnings;

my @ref1;

for (my $i = 0; $i < 3; $i++){
$ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
}
&{$ref1[1]};


for (my $j = 0; $j < 3; $j++){
my $i = $j;
$ref1[$i] = sub{print "Case 2 = $i OK\n"};
}
&{$ref1[1]};
__END__

What is wrong with "Case 1"? I first encountered the problem in a Tk
application and wrote this case as a aide to solving it. The successful
"Case 2" was found by trial and error. Both appear to conform to
section of "Programming Perl" on closures.


Thanks guys, now I see my problem. I had recognized that the scope of
$i was different in Case 2, but failed to see why that made a
difference. Perl has to make a copy of a referenced variable when it
goes out of scope, not when its value changes.

Special thanks to Ben for pointing out the subtle advantage of the Perl
style loop statement. My Case 1 works fine with the new style loop.

Thanks again,
Bill
 
U

Uri Guttman

BS> BS> --snip background info.
use strict;
use warnings;

my @ref1;

for (my $i = 0; $i < 3; $i++){
$ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
}
&{$ref1[1]};


for (my $j = 0; $j < 3; $j++){
my $i = $j;
$ref1[$i] = sub{print "Case 2 = $i OK\n"};
}
&{$ref1[1]};
__END__
BS> Thanks guys, now I see my problem. I had recognized that the scope of
BS> $i was different in Case 2, but failed to see why that made a
BS> difference. Perl has to make a copy of a referenced variable when it
BS> goes out of scope, not when its value changes.

BS> Special thanks to Ben for pointing out the subtle advantage of the Perl
BS> style loop statement. My Case 1 works fine with the new style loop.

did you properly test it as i asked? are all your closures printing
their correct values?

someone did post the correct way to do this with a closure generator
sub. this causes each closure to have its own private $i and they aren't
shared. this is because you enter the generator sub and get a fresh $i
each time. in your code you have a single $i and all the closures see
just it.

uri
 
B

Bill Smith

Uri Guttman said:
BS> My "Case 1" creates an array of references to anonymous subroutines.
BS> Each subroutine is intended to print the value of the loop index ($i)
BS> when it (the subroutine) was created. One of the subroutines is
BS> executed to demonstrate that the wrong value is printed. (The index is
BS> never 3)

BS> "Case 2" is an experimental version which solves the problem by using
BS> another variable ($j) as the loop index
BS> and making $i a lexical variable within the scope of the loop.

BS> my @ref1;

BS> for (my $i = 0; $i < 3; $i++){
BS> $ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
BS> }
BS> &{$ref1[1]};

the $i in the loop and closure are the SAME variable. so $i will end up
at 3 as it has to fail the loop. all of the closures will print three in
this case (try it) as they share the same variable as well.

I tried all the closures for both cases before I posted my question.
I only included one of each in my example because
it seemed to demonstrate the problem.
BS> for (my $j = 0; $j < 3; $j++){
BS> my $i = $j;
BS> $ref1[$i] = sub{print "Case 2 = $i OK\n"};
BS> }


you copied the value before it hit 3 but even so, all the closures will
print 2. you never printed the closure earlier in the list and haven't
seen that bug yet.

I disagree. The code above prints "Case 2 = 1 OK"
If it had printed 2 it would not have been "OK".
The two closures, which are not tested in the example,
print the values 0 and 2 as required.

BS> What is wrong with "Case 1"? I first encountered the problem in a Tk
BS> application and wrote this case as a aide to solving it. The successful
BS> "Case 2" was found by trial and error. Both appear to conform to
BS> section of "Programming Perl" on closures.

case 2 is not successful, you just didn't test it enough.

See comment above.
the variable being closed upon is visible to all closures created where
that variable is in scope. ATM, i can't think of a simple way to do what
you want. perhaps you have an XY problem and think you need to solve it
this way but really don't? what is the original problem?
You are right here. I have already found a better solution to the
original problem.
http://jobs.perl.org
 
A

Anno Siegel

Uri Guttman said:
BS> My "Case 1" creates an array of references to anonymous subroutines.
BS> Each subroutine is intended to print the value of the loop index ($i)
BS> when it (the subroutine) was created. One of the subroutines is
BS> executed to demonstrate that the wrong value is printed. (The index is
BS> never 3)

BS> "Case 2" is an experimental version which solves the problem by using
BS> another variable ($j) as the loop index
BS> and making $i a lexical variable within the scope of the loop.

BS> my @ref1;

BS> for (my $i = 0; $i < 3; $i++){
BS> $ref1[$i] = sub{print "Case 1 = $i WRONG\n"};
BS> }
BS> &{$ref1[1]};

the $i in the loop and closure are the SAME variable. so $i will end up
at 3 as it has to fail the loop. all of the closures will print three in
this case (try it) as they share the same variable as well.

BS> for (my $j = 0; $j < 3; $j++){
BS> my $i = $j;
BS> $ref1[$i] = sub{print "Case 2 = $i OK\n"};
BS> }


you copied the value before it hit 3 but even so, all the closures will
print 2. you never printed the closure earlier in the list and haven't
seen that bug yet.

There is no bug. "my $i" happens before each closure is dispatched,
so it conserves the value $j had at that time.

$_->() for @ref1;

prints

Case 2 = 0 OK
Case 2 = 1 OK
Case 2 = 2 OK

just like it should.

Anno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,070
Latest member
BiogenixGummies

Latest Threads

Top