Beefy Boxes and Bandwidth Generously Provided by pair Networks kudra
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Named anonymous subs

by ysth (Canon)
on Nov 05, 2003 at 22:06 UTC ( #304883=perlmeditation: print w/ replies, xml ) Need Help??

Here's an example of an anonymous sub generator:
use strict; use warnings; use Carp 'cluck'; sub make_incrementor { my ($initial_value, $reset_value) = @_; my $i = $initial_value; sub { if ($i == $reset_value) { cluck('Iterator overuse'); $i = $initial_value; } return $i++; } } sub test_incrementor { my $foo_incrementor = make_incrementor(0,2); print "call $_: got ", $foo_incrementor->(), "\n" for 1..3; } test_incrementor;
When you are debugging it, the debugger shows the anonymous sub as a stringified code ref, which can be less than helpful if you are stepping through the code and have a number of different kinds of anonymous subs.
$ perl -d inc.pl

Loading DB routines from perl5db.pl version 1.19
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(inc.pl:23):      test_incrementor;
  DB<1> c 11
call 1: got 0
call 2: got 1
main::CODE(0x10326b1c)(inc.pl:11):               cluck('Iterator overuse');
  DB<2>

Fortunately, there is a way to tell the debugger a name to show for an anonymous sub. Adding local $DB::sub = 'iterator' at the top of the anonymous sub (also having another mention somewhere of $DB::sub to prevent a warning when not run with -d) gives the debugger a name for it:
main::incrementor(inc.pl:13):            cluck('Iterator overuse');
But when using stack traces for debugging (e.g. cluck), it calls it just __ANON__.
call 1: got 0
call 2: got 1
Iterator overuse at inc.pl line 13
        main::__ANON__() called at inc.pl line 22
        main::test_incrementor() called at inc.pl line 25
call 3: got 0
A little exploration of perl internals shows an undocumented feature. When you create an anonymous sub actually creates an *__ANON__ glob in the package and uses that for the name. This means you can add local *__ANON__ = "incrementor"
in the sub {} to give it a real name while it is running.
What does that buy you, since it already says "at inc.pl line 22"? Well, if the sub{} is in a string eval, you won't have an actual code line. Also, you can include extra information for closures like local *__ANON__ = "incrementor_init${initial_value}_reset$reset_value"

Note that this is an example of a harmless use of an undocumented feature. You use it for debugging only, and if perl stops working this way, the *__ANON__ setting does nothing harmful.

(Given time, I will post more later about my attempts to attach a different *__ANON__ to each sub {} at compile time, which led to this question. For those who experiment, I will note now that the sub doesn't hold a refcnt on the *__ANON__ glob, so you have to arrange some other way to keep it allocated.)

--
Online Fortune Cookie Search
Office Space merchandise

Comment on Named anonymous subs
Select or Download Code
Re: Named anonymous subs
by Ovid (Cardinal) on Nov 05, 2003 at 22:43 UTC

    That's a nice thing to know. Thank you! I've been bitten by errors reported in anonymous subs and this should make life easier.

    #!/usr/local/bin/perl use warnings; use strict; use Carp 'confess'; sub foo { sub { local *__ANON__ = 'local_subref'; confess "Ovid"; }; } eval foo()->();

    Cheers,
    Ovid

    New address of my CGI Course.

Re: Named anonymous subs
by blokhead (Monsignor) on Nov 05, 2003 at 23:39 UTC
    Wow, this is a very interesting trick. I like it a lot. But it's too bad it doesn't help DProf give useful information for anonymous subs:
    use Carp; my $i = 0; sub foo { sub { local *__ANON__ = "local_subref_" . $i++; carp "blokhead"; }; } foo()->() for 1 .. 3; __OUTPUT__ $ perl -d:DProf test.pl blokhead at test.pl line 7 main::local_subref_0() called at test.pl line 12 blokhead at test.pl line 7 main::local_subref_1() called at test.pl line 12 blokhead at test.pl line 7 main::local_subref_2() called at test.pl line 12 blokhead at test.pl line 7 main::local_subref_3() called at test.pl line 12 $ dprofpp tmon.out Total Elapsed Time = 0.079705 Seconds User+System Time = 0.049705 Seconds Exclusive Times %Time ExclSec CumulS #Calls sec/call Csec/c Name ... 0.00 - 0.020 3 - 0.0066 main::__ANON__ ...
    Boo!! DProf still plops everything into main::__ANON__. I've run into this problem before trying to profile code that uses liberal amounts of anonymous subs, and it's quite annoying. Unfortunately I don't know enough about the internals of Perl and DProf to figure out a "clever" solution like yours that works for profiling code (other than using other *Prof modules)...

    blokhead

      As maintainer of Class::MethodMaker, I have more than a passing interest in this technique: C::MM generates anonymous routines by the bucket-load, and as well as debugging, I'm particularly interested in profiling, too.

      I'd been meaning to do something about this for a couple of years now, and this thread finally galvanized me into action. After two days of tinkering with it, and several false starts, I've finally come up with this XS incantation:

      void set_sub_name(SV *sub, char *pname, char *subname, char *stashname) CODE: CvGV((GV*)SvRV(sub)) = gv_fetchpv(stashname, TRUE, SVt_PV); GvSTASH(CvGV((GV*)SvRV(sub))) = gv_stashpv(pname, 1); GvNAME(CvGV((GV*)SvRV(sub))) = savepv(subname); GvNAMELEN(CvGV((GV*)SvRV(sub))) = strlen(subname);

      The pname is the package name, the subname the subname, and stashname is the name of a stash to generate to attach the code to, to avoid overwriting the original stash (which is the package the code was compiled in), or the ANON entry in the pname stash. The above certainly appears to work, both with stack traces & the profiler, and doesn't break my code. I was concerned that generating a new stash for each subr might hurt the memory consumption, but empirical testing suggests that the effect is minimal-to-nil.

      I'm no XS programmer, so I'd appreciate any constructive feedback anyone has.

        I ran into similar issues when developing the Perl 6 metamodel stuff for Pugs, and more recently with Class::MOP. I use the Sub::Name module which has worked great for me, it is also written in XS, you might be able to get a few pointers out of the code (or better yet, just use it, why reinvent the wheel).

        Of course I might also be 2 years to late with this comment too :)

        -stvn
Re: Named anonymous subs
by tilly (Archbishop) on Nov 06, 2003 at 00:57 UTC
    Nice. Let me just fill in a tip that I got from merlyn on how to make your eval's have nice messages. Just use a line directive in a comment as described at the end of perlsyn. See Class::FlyweightWrapper for an example where I do that. Any errors in the classes that I generate with eval there will appear with messages identifying clearly what the purpose of that eval is, and hopefully with enough information to be able to go in and figure out what is messed up in the eval that was called.
Re: Named anonymous subs
by Julian Mehnle (Initiate) on Mar 08, 2006 at 02:35 UTC
    It seems you can even assign namespaced sub names to *__ANON__ and have it work:
    $ perl <<EOF package Foo::Bar; print sub { local *__ANON__ = "boo"; (caller(0))[3] }->(); EOF Foo::Bar::boo $ perl <<EOF package Foo::Bar; print sub { local *__ANON__ = "Foo::Zip::boo"; (caller(0))[3] }->(); EOF Foo::Zip::boo
Re: Named anonymous subs
by ysth (Canon) on Nov 06, 2013 at 22:38 UTC
    I notice this is Hack #57 in "Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving".
    --
    A math joke: r = | |csc(θ)|+|sec(θ)|-||csc(θ)|-|sec(θ)|| |

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://304883]
Approved by hossman
Front-paged by valdez
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (8)
As of 2014-04-18 23:44 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (473 votes), past polls