Hack 56. Deparse Anonymous Functions


Inspect the code of anonymous subroutines.

Perl makes it really easy to generate anonymous subroutines on the fly. It's very handy when you need a bunch of oh-so similar behaviors which merely differ on small points. Unfortunately, slinging a bunch of anonymous subroutines around quickly becomes a headache when things go awry.

When an anonymous sub isn't doing what you expect, how do you know what it is? It's anonymous, fer cryin' out loud. Yet Perl knows what it isand you can ask it.

The Hack

Suppose that you've written a simple filter subroutine which returns all of the lines from a file handle that match your filter criteria.

sub filter {     my ($filter) = @_;     if ('Regexp' eq ref $filter)     {         return sub         {             my $fh = shift;             return grep { /$filter/ } <$fh>;         };     }     else     {         return sub         {             my $fh = shift;             return grep { 0 <= index $_, $filter } <$fh>;         };     } }

Using the subroutine is simple. Pass it a precompiled regex and it will return lines which match the regular expression. Pass it a string and it will return lines which contain that string as a substring.

Unfortunately, later on you wonder why the following code returns every line from the file handle instead of just the lines which contain a digit:

my $filter = filter(/\\d/); my @lines  = $filter->($file_handle);

Data::Dumper is of no use here:

use Data::Dumper; print Dumper( $filter );

This results in:

$VAR1 = sub { "DUMMY" };

Running the Hack

Using the Data::Dump::Streamer serialization module allows you to see inside that subroutine:

use Data::Dump::Streamer; Dump( $filter );

Now you can see the body of the subroutine more or less as Perl sees it.

my ($filter); $filter = undef; $CODE1 = sub {            my $fh = shift @_;            return grep({0 <= index($_, $filter);} <$fh>);          };

From there, it's pretty apparent that Perl didn't recognize that you were trying to pass in a regular expression and the bug is trivial to fix:

my $filter = filter(qr/\\d/); my @lines  = $filter->($file_handle);

Hacking the Hack

Behind the scenes, Data::Dump::Streamer uses the core module B::Deparse. In essence it does the following:

use B::Deparse; my $deparse = B::Deparse->new( ); print $deparse->coderef2text($filter);

which outputs:

{     my $fh = shift @_;     return grep({0 <= index($_, $filter);} <$fh>); }

The primary difference is that Data::Dump::Streamer also shows the values of any variables that the subroutine has closed over. See "Peek Inside Closures" [Hack #76] for more details. This technique is also good for displaying diagnostics when you eval code into existence or receive a subroutine reference as an argument and something goes wrong when you try to execute it.

The B::Deparse documentation gives more information about the arguments that you can pass to its constructor for even better control over the output.



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