lbrandewie has asked for the wisdom of the Perl Monks concerning the following question:

Hey Perl Monks,

I, a poor hacker, am confounded by the following problem. This isn't the problem I'm having (exactly), it's just the simplest case I can reduce it to.

Let's say you wanted to build a simple text file editor, using the following code:

use Tkx; use strict; our (@data, @editctls, @buttons); open IN, "test.txt"; flock IN, 1; @data = <IN>; close IN; chomp @data; our $mw = Tkx::widget->new("."); for (my $x = 0; $x < @data; $x++) { push @editctls, $mw->new_ttk__entry(-width => 30, -textvariable => + \$data[$x]); $editctls[$x]->g_grid(-row => $x, -column => 0); push @buttons, $mw->new_ttk__button(-text => "Update", -command => + sub { update($x); } ); $buttons[$x]->g_grid(-row => $x, -column => 1); } Tkx::MainLoop(); sub update { my $which = shift; open DATA, "+<test.txt"; my @stuff = <DATA>; $stuff[$which] = $data[$which] . "\n"; seek DATA, 0, 0; print DATA @stuff; truncate DATA, tell(DATA); close DATA; }

And I'm using the following text file for testing:

this is a text file

The problem is with the update buttons. When clicked, they send the current value of $x to the update subroutine, not the value of $x when the anonymous sub was created. I had thought that when anonymous subs refer to lexical variables, they created a closure, but apparently not so in this case. Am I missing something? Is there a correct way to do this?

I did try moving the control creation logic to an actual sub but it made no difference.

Thanks,

Lars Brandewie

Replies are listed 'Best First'.
Re: Closure confusion in Tkx (declarations in loops)
by LanX (Saint) on Jul 13, 2019 at 12:50 UTC
    Hi

    I'm not an expert on Tkx, but I think you might get bitten by the way closure variables are "shared".

    Please compare

    use strict; use warnings; # ---------- for each @list my @each; for my $i ( 1..3 ) { push @each, sub { warn "each: \t$i" }; } $_->() for @each; # ---------- for c-style my @cfor; for ( my $i = 1 ; $i < 4; $i++ ) { push @cfor, sub { warn "cfor: \t$i" }; } $_->() for @cfor; # ---------- generator sub gen { my ($x)=@_; return sub { warn "gen: \t$x" }; } my @gen; for ( my $i = 1 ; $i < 4; $i++ ) { push @gen, gen($i); } $_->() for @gen;

    each: 1 at d:/exp/pm_closure.pl line 10. each: 2 at d:/exp/pm_closure.pl line 10. each: 3 at d:/exp/pm_closure.pl line 10. cfor: 4 at d:/exp/pm_closure.pl line 21. cfor: 4 at d:/exp/pm_closure.pl line 21. cfor: 4 at d:/exp/pm_closure.pl line 21. gen: 1 at d:/exp/pm_closure.pl line 31. gen: 2 at d:/exp/pm_closure.pl line 31. gen: 3 at d:/exp/pm_closure.pl line 31.

    actually your case with a c-style loop is how it's supposed to be, your my $x has only one instance and only gets a new value with each iteration.²

    for (@list)' is special because the loop var is an alias to the list-value. °

    To avoid such confusions it's always safe to call a "generator" sub which returns a closure.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

    footnotes

    °) the source of the famous "want stay shared" error. (UPDATE or because it's considered an inside declaration ?)

    ²) a c-style for is semantically just a while-loop with a prior declaration.

    updates
    expanded example with clearer output and generator case
      > ²) a c-style for is semantically just a while-loop with a prior declaration.

      to expand further on this, please see perlsyn#For-Loops

      which means

      for (my $i = 1; $i < 10; $i++) { ... }
      is the same as this:

      { my $i = 1; while ($i < 10) { ... } continue { $i++; } }

      now compare the different effects from an inner declaration ($i) and an outer declaration ($x)

      use strict; use warnings; # ---------- for c-style my @cfor; for ( my $x = 1 ; $x < 4; $x++ ) { my $i = $x; push @cfor, sub { warn "cfor: \ti=$i \tx=$x" }; } $_->() for @cfor; # ---------- while my @values = 1..3; my @while; my $x; while ( my $i = shift @values ) { $x = $i; push @while, sub { warn "while: \ti=$i \tx=$x" }; } $_->() for @while;
      cfor: i=1 x=4 at d:/exp/pm_closure2.pl line 11. cfor: i=2 x=4 at d:/exp/pm_closure2.pl line 11. cfor: i=3 x=4 at d:/exp/pm_closure2.pl line 11. while: i=1 x=3 at d:/exp/pm_closure2.pl line 25. while: i=2 x=3 at d:/exp/pm_closure2.pl line 25. while: i=3 x=3 at d:/exp/pm_closure2.pl line 25.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

        Thanks Rolf!

        The code works now but I'm still kinda puzzled as to why the c-style for fails in this case. Is this not a bug in perl?

        Regards,

        Lars

Re: Closure confusion in Tkx
by AnomalousMonk (Archbishop) on Jul 13, 2019 at 13:37 UTC

    I don't know if Tkx is the same (I suspect it would be), but in Perl::Tk there is the "callback" (see Tk::callbacks), an array reference that encapsulates a function reference and a set of aguments:

    c:\@Work\Perl\monks>perl -wMstrict -le "use constant Y => 'bar'; ;; my @callbacks; ;; my $z = 9; ;; for (my $x = 1; $x < 4; $x++) { push @callbacks, [ \&foo, $x, Y, $z-- ]; } ;; $z = 'zero'; ;; $_->[0]->(@$_[ 1 .. $#$_ ]) for @callbacks; ;; sub foo { print qq{@_}; } " 1 bar 9 2 bar 8 3 bar 7
    (Don't worry about the messy  $_->[0]->(@$_[ 1 .. $#$_ ]) stuff in the example; that's all taken care of under the hood in Tk and is only intended to illustrate the principle.)

    Update: Changed example code to include  $z variable.


    Give a man a fish:  <%-{-{-{-<

Re: Closure confusion in Tkx
by BillKSmith (Monsignor) on Jul 13, 2019 at 13:58 UTC
    Many years ago, I asked a similar question on PerlGuru. Since then, I almost never use the c-style "for". I had completely forgotten the reason. Thanks for the reminder.
    Bill
      Please note that copying to an inner declaration works
      for ( my $x = 1 ; $x < 4; $x++ ) { my $i = $x; push @cfor, sub { warn "cfor: \ti=$i \tx=$x" }; }

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice