http://qs1969.pair.com?node_id=52096

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

This is actually a two-part question:

1. Can Perl be coaxed into calling an anonymous sub as if the sub were in a package than it's own? For example:

package Foo; my $coderef = sub { $global }; $global = 'in Foo'; package Bar; $global = 'in Bar'; print &$coderef;
This code prints 'in Foo', but I would like a way of getting it to print 'in Bar'.

I don't want to go the route of defining code as a string for eval and then prepending a package line to the code string... things would be much cleaner if I could use anonymous sub refs instead.

2. Callback routines for this module should be defined as anonymous subs, ideally mimmicking the way map, grep, and sort work (except, of course, that a sub would be needed before the code block). The user of the module should not have to include code to set a bunch of parameters from @_ when they write their callbacks, as I want callbacks to be lean and simple. Ideally, I want parameters pre-prepared in a "throw-away" package that their subs would get called in (as above), but if that won't work, I'm considering the possibility of callbacks getting their parameters prepared in a hash referenced by a localized $_ (for example $$_{name}). Are there any pitfalls or drawbacks to doing things this way. Which would you consider the most "perlish" way to accomplish my goals?

Thanks monks!

Replies are listed 'Best First'.
Re: Callback Design
by saucepan (Scribe) on Jan 16, 2001 at 04:34 UTC
    1. This dirty bit of nastiness appears to do what you want:
    package Foo; my $coderef = sub { no strict 'refs'; ${ (caller)[0] . "::global" } }; $global = 'in Foo'; package Bar; $global = 'in Bar'; print &$coderef, "\n";
    ..but why on earth do you want to do this? :)

    This kind of magic side-effect can cause incredibly hard-to-diagnose bugs: someone might move the subroutine call into another package, never suspecting that it might vary it's behaviour based on something as oblique as compiler directives in effect in the most-recent calling code.

    2. Using $_ for callbacks is fine AFAIK, as long as you are careful and the callbacks are short. I've used things like hostname_cleanup => sub { s/test\.userfriendly\.org$//i } to pass regexps in from configuration files before I learned about qr//. I too will be interested to see what the real experts think!

      Impressively diry and nasty :) What I'm looking for is a way to keep the anonymous sub the same, and override it's package when it is called. The idea is to have callback params readily available as package variables.

      I believe the Safe module could do this, but it seems like terrible overkill.

        Hmm. Let me see if I understand: you want to be able to do something like grep or map, where your code gets passed a subref which you then call back?

        If so, you don't need to worry about changing packages at the time you call it, since it will have already picked up the proper package when it was defined (and compiled) in the caller's code:

        package my; $global = 'in_my'; sub own_map(&@) { my $code = shift; my @return = (); push @return, $code->() for @_; @return; } package main; $global = 'in_main'; print my::own_map { $_, " ", $global, "\n" } (1..20); # prints "1 in_main\n", "2 in_main\n" and so on
        In the above code, my::own_map could have molested ${ (caller)[0] . "::global" } to pass a value in to the block. I guess this might be OK (although it's clearly an abuse of caller, which AFAIK was intended for debugging), as long as it's well documented and you are careful to localize everything.

        Updated: removed a confusing and unneeded $_ argument on the line that calls the callback. Oops!

(Ovid warning: bad code ahead) Re: Callback Design
by Ovid (Cardinal) on Jan 16, 2001 at 06:08 UTC

    Warning: Bad code ahead!

    Just messing around (free time at work). I came up with the following, but I do NOT recommend it :)
    my $coderef = sub { my ($package) = caller; $package->global }; package Base; sub AUTOLOAD { no strict 'refs'; # Please note the complete lack of # Sanity checking here :) return ${ $AUTOLOAD } if $AUTOLOAD =~ /^.*::\w+$/; } package Foo; @ISA=('Base'); $global = 'in Foo'; print &$coderef; package Bar; @ISA=('Base'); $global = 'in Bar'; print &$coderef;
    Like I said, it's just me messing around. It would be an interesting way to getting to package globals, but it's more of me just playing around rather than offering it as a serious programming solution. It is BAD CODE.

    And yes, the dot star is there on purpose. It'll catch package names like Foo::bar::baz.

    Overall, this sort of problem is reminiscent of the "variable variable" problem as is indicative of underlying design issues that should be addressed. Be very careful of treading on this thin ice.

    Cheers,
    Ovid

    Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Re: Callback Design
by repson (Chaplain) on Jan 16, 2001 at 08:43 UTC
    The problem is that all coderefs work like closures. When a piece of code is complied the the variable $foo in it, it will not store the name $foo, but rather the current position in the parse tree that $foo would refer to in normal usage. When this piece of compiled code is passed into a subroutine in any package it isn't changed, so the place that said $foo still refers to the variable which was complied into the code.

    The map, sort and grep callbacks use the variables $_, $a and $b which are special cases, where their usage always refers to the same position in the parse tree (though that can be displaced with local, but we won't go into that).

    On the other hand callbacks for modules such as HTML::Parser and Tk are passed a full list in @_ (using $code_ref->('foo',3,'blah');) which means those callbacks generally begin with my($foo,$blah)=@_; to extract that data.

    In your case I'd say your best bet may be to stick to using $_, something like this.

    $_ = 'hi'; my $code = sub { print $_->{blah} . "\t" . $_->{moo} . "\n" }; Bar::hi($code); print $_ . "\n"; # prints 'hi' package Bar; sub hi { my $coderef = shift; local $_ = { 'foo' => 2, 'blah' => 5, 'moo' => 'blah' }; &$coderef; # prints '5 blah' }