Any way to "inject" an INIT block into a different module?

Matt Lawrence matt.lawrence at virgin.net
Fri May 30 14:06:02 BST 2008


untested stab-in-the-dark:

BEGIN { our $in_begin = 1 }
INIT {
    our $in_begin = 0;
    ...
}
sub import {
    if (our $in_begin) {
        push @HOOKS, ... ;
    }
    else {
        export ( ... )
    }
}

Matt

Andy Wardley wrote:
>
> I'm messing with INIT blocks, trying to get some methods 
> auto-generated at
> the right time.
>
> Here's an example.  Let's say I have an amplifier.  Naturally, it goes up
> to eleven.
>
>     package Amp;
>     use Control qw( volume sustain );
>
>     sub volume {
>         print "This amp goes up to eleven\n"
>     }
>
> My Control module looks something like this:
>
>     package Control;
>
>     our @HOOKS;
>     INIT { export(@_) for @HOOKS; }
>
>     sub import {
>         my $class = shift;
>         push(@HOOKS, [(caller(0))[0], @_]);
>     }
>
>     sub export {
>         my $target = shift;
>         no strict 'refs';
>
>         foreach my $sub (@_) {
>             if (defined &{"${target}::$sub"}) {
>                 print "* skipping ${target}::$sub (already defined)\n";
>             }
>             else {
>                 print "* creating ${target}::$sub\n";
>                 *{"${target}::$sub"} = sub {
>                     print "Control $sub\n";
>                 };
>             }
>         }
>     }
>
> It creates simple accessor methods in the Amp class, or indeed any class
> that you C<use> it in.  In this case, they're just dummy subs, but you
> get the idea.
>
> Because I don't want these auto-generated methods to overwrite any 
> existing
> methods that I've written (like volume()), we skip over any methods that
> are already defined.  But to do this, we have to wait until the INIT 
> phase
> to give Perl a chance to parse all the source code and figure out what
> methods have or haven't been defined.  So the import() method pushes the
> stuff onto @HOOKS for the INIT block to subsequently pipe back via 
> export().
>
> So far so good (or evil, depending on your proximity to the crack pipe).
> This small test program:
>
>     use Amp;
>     Amp->volume;
>     Amp->sustain;
>
> Generates this output:
>
>     * skipping Amp::volume (already defined)
>     * creating Amp::sustain
>     This amp goes up to eleven
>     Control sustain
>
> At first glance this is most satisfactory.  If I'm on already on ten, all
> the way up, on ten on my guitar, and I need that extra push over the 
> cliff,
> I can go up to eleven.  And I could go away and have a cup of tea and 
> still
> be hearing that sustain.
>
> However, behind the glitzy facade of the topsy-turvy world of 
> rock'n'roll,
> there limps a small and fragile pony over-burdened with a heavy load 
> of FAIL.
> If you try and load the Amp module dynamically (let's say we're 
> starting a
> band and we haven't yet decided if we're going to be electric or 
> acoustic),
> like so:
>
>     use Control qw( sex drugs );
>
>     # later...
>     eval "use Amp";
>     Amp->volume;
>     Amp->sustain;
>
> Then it blows chunks thusly:
>
>     This amp goes up to eleven
>     Can't locate object method "sustain" via package "Amp" at ...blah
>
> Because, of course, the INIT time for my Control class has come and gone.
>
> Now I realise that I can work around this by forcing the caller to do 
> some
> extra work, e.g. define their own INIT block or call a specific method
> to generate the methods at the right time.  But I'm trying to figure out
> if there's a way around it without requiring any extra work in the 
> caller's
> package.  If only for chuckles.
>
> What I think I want to do, in conceptual terms at least, is to have my
> Control module "export" an INIT block/action into the Amp module.  I know
> I can't do that (at least I don't think I can, can I?) but that's the 
> effect
> that I'm after.
>
> Any ideas?
>
> A
>
>
>



More information about the london.pm mailing list