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

Andy Wardley abw at wardley.org
Fri May 30 13:26:36 BST 2008


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