views:

150

answers:

2

Help Perlers! Does anyone know a simple "insert code here" approach would bring code from external files once at compile time into a package?

Background:

I have a standard PBP-style inside-out class that is getting quite large. I want to split the code into multiple files but not extend the class. Optimally, I would simply insert the code into the "Magic JuJu" section of the example module (see below) once at compile time.

I have looked at AutoLoader as a means to get this accomplished. However, there are two things that give me pause. If I could get around these, it might be a near-optimal solution:

  1. I don't want to split every little sub into a separate file; just a few more reasonably sized files (using sub stubs in the caller is fine, though); and

  2. I don't want to defer compile on every sub; some subs I'd like to have compiled on initial use. However, this is probably not a deal breaker.

I know Moose provides "Roles", which I believe does this well, but for various reasons, Moose is not an option for this project, nor is Mouse.

I have used "require q(some/file)" in the "Magic JuJu" location, but that does not maintain the persistent variable scope, i.e. subs from the external file don't "see" the object attribute hashes correctly (in still other words, putting the require at the top of the file would have the same effect). I could get around that by always using setters and getters. So that is not a deal breaker, but would require a bit of coding time and execution overhead that I'd rather not incur.

Finally, I don't want to extend the class; it already has multiple inheritance. I just want a simple "insert code here" approach would bring the code in once at compile time.

In summary:

  1. (Required) imports code from external files into package namespace
  2. (Required) Does so only at compile-time or minimal run-time overhead
  3. (Required) Does not extend class
  4. (Desired) Honors insert location scope

Example Code with "Magic JuJu" comment below:

package T;

use strict;
use warnings;

########## BEGIN object persistent variables scope block ############
{
  my %Attr_Name_Env;

  ## Constructor 'new'
  #
  sub new {
    ## Get and confirm arguments
    #
    my $class      = shift;
    my $href_arg = {@_};
    my $name_env = $href_arg->{'name_env'};

    ## Bless anon scalar into class
    #
    my $obj_new   = bless anon_scalar(), $class;
    my $idx_self  = ident $obj_new;

    # Populate object attributes
    #
    $Attr_Name_Env{ $idx_self } = $name_env;

    return $obj_new;
  }
  ## END Constructor 'new'

  sub DESTROY {... as you do ...}

  sub t_get_name_env {
    my $self      = shift;
    my $idx_self  = ident $self;
    return $Attr_Name_Env{ $idx_self };
  }

  ## insert magic juju here

}
########## END object persistent variables scope block ############

1;

Maybe a BEGIN block with a slurp and eval ...

+1  A: 

If you are willing to live with the consequences, there is Filter::Macro.

Sinan Ünür
No, not filters! The horror, the horror!
Robert P
Hi Sinan! Thank you! Filter::Macro certainly looks like a good choice. You mention the consequences, above. Could you share with me what those might be?
Michael Mikowski
@Michael: You will be using a source filter. ;-) `Filter::Macro` is straightforward enough that I do not think it will be a problem, but source filters tend to be a little fragile and I don't know if `Filter::Macro` is safe to use with every other module out there.
Sinan Ünür
Thanks Sinan! I'm checking out the method above at present, but may return to Filter::Macro.
Michael Mikowski
+2  A: 

You could just use the modules and import the desired subs.

use MyMod qw( wanted_sub_1 wanted_sub2 );

To get access to your attribute hashes you will need to modify import for your target modules.

package MyClass;
use MyMod { attrib1 => \%attrib1, attrib2 => \%attrib2 }, qw( wanted_sub1 wanted_sub2 );

MyMod's import would then create code refs that are closed over the initial hash argument, and install those into the MyClass namespace.

daotoad
1. The attributes are lexically scoped through a containing block, so they would not be available for MyMod unless that containing block is removed. This would make the attributes not truly private, but that might be ok. Or one could use only accessors for attributes.2. MyMods subs will want to use methods from the class, especially the inherited methods. I think that will work with the standard `my $self = shift` approach, but will need to test.3. A little verbose, but perhaps is most correct. Will report back. The more I read, the more I fear source filters
Michael Mikowski
I have it working using this method! The import method is a bit obtuse for the average developer, and it is a little verbose. But it's clean and it works, and certainly very perl-ish. Besides, perl -cw works on each module, which is very nice. Filter::Macro failed to work, and looks a bit too magical for this application.
Michael Mikowski
BTW, to see the actual working code, go here: http://perlmonks.org/?node_id=823764
Michael Mikowski
Thanks for posting the code. I felt bad just giving an outline, but I've got a cold and the old brain didn't have the pep to produce it for you. Custom import routines let you do all kinds of magic. They can be quite fun.
daotoad