Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Odd lexical variable behavior -- why does this happen?

by radiantmatrix (Parson)
on Sep 12, 2007 at 20:08 UTC ( [id://638668]=perlquestion: print w/replies, xml ) Need Help??

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

I ran across some interesting behavior with lexically-scoped variables; it first struck me as so odd that I thought I'd discovered a bug in my stack1. However, I couldn't believe that I was doing something so unusual that I was the first to discover such a bug, so I built a smaller test case. What results is not, I think, a bug in the stack (oh, yes it is, sort of: see this reply), but is still behavior I don't entirely understand. Perhaps some more enlightened monk can assist me.

I know, of course, that when I store a reference to a lexically-scoped value, then let the original go out of scope, the reference still exists, so the variable is not entirely destroyed. However, it surprised me to learn that I can enter that scope again, specifically set the value of the variable, and not have it work. Check it out:

#!/usr/bin/perl package main; use strict; use warnings; use Tk; use Tk::ProgressBar; my $mw = MainWindow->new(-title=>"Bug Demo"); my $pb = $mw->ProgressBar( -width => 20, -length => 200, -blocks => 20, )->pack(); my $button = $mw->Button( -text => 'Press this!', -command => \&count_up, )->pack(); MainLoop; sub count_up { $mw->update; my $progress = 0; $pb->configure( -from => 0, -to => 20, -variable => \$progress, ## store the reference. ); my $cnt = 0; print "\$cnt=$cnt; \$progress=$progress\n"; for (1..20) { $progress++; $cnt++ unless ($_ % 2); $mw->update; } $mw->update; }

Note how I specifically state my $progress = 0; in the sub. If I click the button 3 times, thus calling the sub 3 times, I get the following console output:

$cnt=0; $progress=0 $cnt=0; $progress=20 $cnt=0; $progress=40

I don't understand why that's happening -- if I'm setting $progress to zero each time, why does it always increment in that way? I've managed to refactor the production code that has this issue in such a way as to avoid this behavior, so this isn't life-or-death by any stretch. However, it is really bugging me, and I'd appreciate some enlightenment.

Thanks in advance!

-----

1: i.e. in some module I was using, its dependencies, or perl itself (yes, I know how unlikely the last is).

Updates:

  • 20070913 : added link to solution, thanks to ikegami

<radiant.matrix>
Ramblings and references
The Code that can be seen is not the true Code
I haven't found a problem yet that can't be solved by a well-placed trebuchet

Replies are listed 'Best First'.
Re: Odd lexical variable behavior -- why does this happen?
by jdporter (Paladin) on Sep 12, 2007 at 22:09 UTC

    This appears to be an undocumented oddity of Tk::ProgressBar: when you give it a reference to a variable, it immediately stores the current value into the variable. I don't think widgets should do that, and certainly not without documenting it. But there you go.

    To test this theory, add the following callback on another button:

    sub dont_count_up { my $progress = 0; $pb->configure( -variable => \$progress ); print "progress=$progress\n"; }

    A word spoken in Mind will reach its own level, in the objective world, by its own weight
Re: Odd lexical variable behavior -- why does this happen?
by ikegami (Patriarch) on Sep 12, 2007 at 20:37 UTC

    This sounds very familiar. I bet I'll find that the handler (count_up) is called using MULTICALL. The last time I looked at it, I hypothesised that MULTICALL is an optimization used to speed up calling a function repeatedly by reusing stack frames, and that it fails when a lexical is still referenced when it goes out of scope.

      Ok, so I did some digging. No MULTICALL. I was off track in my first post.

      Changing the var that holds the progress shouldn't change the value of the progress bar, and there's code to makes sure of that.

      Perhaps you want

      use strict; use warnings; use Tk; use Tk::ProgressBar; my $mw = MainWindow->new(-title=>"Bug Demo"); my $progress; my $pb = $mw->ProgressBar( -width => 20, -length => 200, -blocks => 20, -from => 0, -to => 20, -variable => \$progress, ## store the reference. )->pack(); my $button = $mw->Button( -text => 'Press this!', -command => \&count_up, )->pack(); MainLoop; sub count_up { $progress = 0; $mw->update; for (1..20) { select(undef, undef, undef, 0.01); $progress++; $mw->update; } }

        Aha! Thank you!

        The work-around you presented is one approach, my particular answer was to

        $pb->configure( -variable => undef );
        on the way out of scope. Thanks for shedding light on this, I don't know how or why I missed this when I scanned ProgressBar.pm...

        As for "Changing the var that holds the progress shouldn't change the value of the progress bar", I sort of disagree. Since ProgressBar doesn't actually update that variable, I'd expect that changing to a different variable, you'd want to use that variable's value. As the documentation for Tk::ProgressBar says about -variable:

        Specifies the reference to a scalar variable to link to the ProgressBar. Whenever the value of the variable changes, the ProgressBar will upate to reflect this value.

        I'd expect that changing variables entirely would also change "the value of the variable", but obviously others (including the original author) have different expectations.

        Thanks again for the insight. ++!

        <radiant.matrix>
        Ramblings and references
        The Code that can be seen is not the true Code
        I haven't found a problem yet that can't be solved by a well-placed trebuchet
Re: Odd lexical variable behavior -- why does this happen?
by perrin (Chancellor) on Sep 12, 2007 at 20:25 UTC
    I can't see any reason why this would happen. Is this really the entire code needed to produce the problem? Does it still happen if you remove the calls to $pb?
Re: Odd lexical variable behavior -- why does this happen?
by demerphq (Chancellor) on Sep 12, 2007 at 21:28 UTC

    Could you perlbug this please?

    ---
    $world=~s/war/peace/g

Re: Odd lexical variable behavior -- why does this happen?
by shmem (Chancellor) on Sep 16, 2007 at 11:13 UTC
    Dumping the reference with Devel::Peek inside your sub count_up
    sub count_up { $mw->update; my $progress = 0; warn "after \$progress init($counter):\n"; Dump($progress); $pb->configure( -from => 0, -to => 20, -variable => \$progress, ## store the reference. ); my $cnt = 0; print "\$cnt=$cnt; \$progress=$progress\n"; for (1) { $progress++; warn "\$progreess in for() loop($counter):\n"; Dump($progress); $cnt++ unless ($_ % 2); $mw->update; } $counter++; $mw->update; }

    reveals

    that the Progressbar object remembers the value from the previous configured reference via Tie::Watch voodoo, and you can't reset that value by just passing another reference to configure.

    How cool is that? You can't stop progress :-)

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://638668]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (2)
As of 2024-04-20 03:14 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found