Hack 76. Peek Inside Closures


Violate closure-based encapsulation when you really need to.

Very few rules in Perl are inviolatenot even the rule that lexicals are inaccessible outside their scopes. For closures to work (and even lexicals in general), Perl has to be able to access them somehow. If you could use the same mechanism, you could read from and write to these variables.

This is very useful for debugging closures and closure-based objects [Hack #43]. It's scary and wrong, but sometimes it's just what you need.

The Hack

Robin Houston's PadWalker module helpfully encapsulates the necessary dark magic in a single place that, most importantly, you don't have to understand to use. Suppose you have a misbehaving counter closure:[7]

[7] The erroneous operator is ==. There are actually two bugs, though.

sub make_counter {     my ($start, $end, $step) = @_;     return sub     {         return if $start == $end;         $start += $step;     }; }

One way to debug this is to throw test case after test case at it [Hack #53] until it fails and you can deduce and reproduce why. An easier approach is to show all of the enclosed values when you have a misbehaving counter.

Once you have a counter, use PadWalker's closed_over( ) function to retrieve a hash of all closed-over variables, keyed on the name of the variable:

use Data::Dumper; use PadWalker 'closed_over'; my $hundred_by_nines = make_counter( 0, 100, 9 ); while ( my $item = $hundred_by_nines->( ) ) {     my $vars = closed_over( $hundred_by_nines );     warn Dumper( $vars ); }

Running the Hack

Running this reveals that $start, the current value of the counter, quickly exceeds 100.

$VAR1 = {           '$start' => \\9,           '$step' => \\9,           '$end' => \\100         }; $VAR1 = {           '$start' => \\18,           '$step' => \\9,           '$end' => \\100         }; # ... $VAR1 = {           '$start' => \\6966,           '$step' => \\9,           '$end' => \\100         }; # ...

$step and $end are okay, but because $start never actually equals$end, the closure never returns its end marker.

Changing the misbehaving operator to >= fixes this.[8]

[8] Consider if the $step is negative, however.

Hacking the Hack

One good turn of scary encapsulation-violation deserves another. The hash that closed_over( ) returns actually contains references to the closed-over variables as its values. If you dereference them, you can assign to them. Here's one way to debug the idea that the comparision operator is incorrect:

while ( my $item = $hundred_by_nines->( ) ) {     my $vars  = closed_over( $hundred_by_nines );     my $start = $vars->{'$start'};     my $end   = $vars->{'$start'};     my $step  = $vars->{'$step'};     if ( $$start > $$step )     {         $$start = $$end - $$step;     } }

PadWalker is good for accessing all sorts of lexicals. If you have a subroutine reference of any kind, you can see the names of the lexicals within that subroutinenot just any lexicals it closes over. You can't always get the values, though. They're only active if you're in something that that subroutine actually called somewhere.

Be careful, though; just because you can look in someone's closet doesn't mean that you should.

The CPAN module Data::Dump::Streamer can do similar magic, except that it also deparses the closure. This is useful in other circumstances. The code:

use Data::Dump::Streamer; my $hundred_by_nines = make_counter( 0, 100, 9 ); 1 while 100 > $hundred_by_nines->( ); Dump( $hundred_by_nines );

produces the result:

my ($end,$start,$step); $end = 100; $start = 108; $step = 9; $CODE1 = sub {            return if $start = = $end;            $start += $step;                             };




Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net