Greetings fellow monks,

This evening I was in the process of doing some work on a GUI I've been developing, when I came across a very strange phenomenon that I can't explain.  The test program here exhibits the same symptoms I found in the (much longer) original program.  I've kept it as a GUI, but I'm guessing it would be quite easy to duplicate this as a 10-15 line simple Perl script as well.

First of all, here's the program:

#!/usr/bin/perl -w # # 070502 liverpole -- Demonstrates a weirdness which I don't understa +nd # # Strict use strict; use warnings; # Libraries use Tk; ################## ## Main program ## ################## # Construct the GUI my $mw = new MainWindow(); my $f1 = $mw->Frame()->pack; my $chosen = ""; my $fr1 = $f1->Frame()->pack(-side => 'left'); my $fr2 = $f1->Frame()->pack(-side => 'left'); button($fr1, 'green', ' Exit GUI ', sub { $mw->destroy() }); button($fr1, 'green', ' Start test ', sub { start_test($fr2) }); $fr1->Label(-width => 16, -textvar => \$chosen, -bg => 'white')->pack; MainLoop(); ################# ## Subroutines ## ################# sub button { my ($w, $bg, $text, $pcmd) = @_; my $b = $w->Button(-bg => $bg, -text => $text); $b->configure(-width => 16, -command => $pcmd); $b->pack; return $b; } sub randomize_list { my $plist = shift; my $prandom = [ ]; while (@$plist) { push @$prandom, splice(@$plist, int(rand(@$plist)), 1); } return $prandom; } sub start_test { my ($frame) = @_; # Create a random list of 9 items { 1 ... 9 } my @values = qw( 1 2 3 4 5 6 7 8 9 ); my $pvalues = randomize_list([ @values ]); my $chosen_idx = -1; foreach my $num (@$pvalues) { $chosen = $num; # Create a duplicate list (not including the chosen number) my @dups = grep { $_ ne $num } @values; my $pdups = randomize_list([ @dups ]);; # Insert chosen number at a random place { 0 ... 3 } in the li +st my $correct_idx = int(rand(4)); $pdups->[$correct_idx] = $num; my $pbuttons = [ ]; for (my $i = 0; $i < 4; $i++) { my $dup = $pdups->[$i]; my $icopy = $i; # The closure where I'm expecting $chosen_idx to get # assigned to $i = {0,1,2,3}, but it seems to always # get assigned to 4! (or whatever the final value of # $i is.) However, if I set $chosen_idx to $icopy: # # my $psub = sub { $chosen_idx = $icopy }; # # then everything works ... ??? # my $psub = sub { $chosen_idx = $i }; push @$pbuttons, button($frame, 'skyblue', $dup, $psub); } $chosen_idx = -1; while ($chosen_idx < 0) { $mw->update(); } # Delete the buttons map { $_->destroy() } @$pbuttons; # Why on EARTH is this showing $chosen_idx as 4 every time?! print "correct idx[$correct_idx] ... chosen_idx[$chosen_idx]\n +"; } }

When I run it, it creates a small GUI where you click on the "Start test" button, and it displays a single random value from 1-9 in a Label widget on the left, and 4 buttons on the right (one of which contains the same value as in the Label).

Okay, so far so good.  But now, when you click on one of the 4 buttons, it prints to STDOUT what the correct index of the chosen value is (from 0 to 3), and the index of the button you pressed.  However, the second index is always a 4?!

I don't understand why this should be true.  I'm constructing a closure $psub around each value of $i in the loop, which can be verified by printing the value of $i in the button subroutine:

sub button { my ($w, $bg, $text, $pcmd, $ival) = @_; (defined $ival) and print "Value of \$i is $ival\n"; my $b = $w->Button(-bg => $bg, -text => $text); $b->configure(-width => 16, -command => $pcmd); $b->pack; return $b; } # ... and later, when calling button() ... push @$pbuttons, button($frame, 'skyblue', $dup, $psub, $i); # The above correctly displays each value of $i

I've also tried making $i global in case it was somehow related to its scope within the for statement, but that didn't change anything.

The workaround I'm now using is to make a copy of $i called $icopy, and assign to it instead:

my $icopy = $i; # ... my $psub = sub { $chosen_idx = $icopy };

And it's clearly somehow related to the final value of $i at the end of the for loop (to verify this, try changing the loop to for (my $i = 0; $i < 7; $i++) {, for example).

But, can some wise monk please explain to me why the closure is not working the way I'd expect it to, with respect to the interim values if $i??


s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/

In reply to Bizarre Results when Creating a Closure by liverpole

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.