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

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;

Replies are listed 'Best First'.
Re: Import module code from external files?
by chromatic (Archbishop) on Feb 17, 2010 at 20:31 UTC

    What are you trying to accomplish?

    From a technical standpoint, if you have memory constraints... there are better solutions.

    From a maintainability standpoint, you'll have to perform some clever hacks if you want to get access to the hidden lexicals.

    If your class is too large for your taste, it probably does too much.

      Memory isn't too much of a concern -- although we are running in a mod_perl2 environment, we only have 5 child processes on a 12GB machine.

      Startup time isn't too important either (see mod_perl2). Runtime is.

      The module represents the business logic layer (the Model) for a web app. Thus it glues together multiple db objects that need to be coordinated for hierarchy and user access, like accounts-to-items, account-to-files relations, etc. So the logical intent is fairly ok (see the POSIX modules as an example of similar grouping).

      Much of the verboseness arises from quite a few complex sql queries that are quite long, and for readability reasons, we want to keep them in the file. So the module gets big; around 2000 lines.

      Maybe refactoring the class hierarchy is the right thing to do, but there really isn't time to consider that, or move to Moose, at present. Besides, Moose does a lot of the same stuff "under the sheets" with "roles", I'd bet.

      I tried the "require" trick, autoloader (shudder), using globals (shudder), Mouse, Filter::Macro, and finally Export with a param list. The last one seems to be a winner; it's only a little bit magical, and its all "real" perl -- all modules pass the perl -cw test. The import and exporter stuff takes up about 20 lines per "split" file but I can write methods there almost exactly as in the main file.

      The script

      #!/usr/bin/env perl use strict; use warnings; use lib q(./); use T4; ## Main # my $obj_t = T4->new( name_env => q(foo) ); print q(>> ) . $obj_t->t_hello_world . qq( <<\n); $obj_t->t_set_remote(q(This is a test)); print q(>> ) . $obj_t->t_get_remote . qq( <<\n); ## END Main

      The Core Module

      package T4; # Pragmas use strict; use warnings; use version; our $VERSION = qv('3.0.0'); use feature qw(:5.10); # Standard Modules use Data::Dumper qw(Dumper); use Class::Std::Utils qw( ident anon_scalar ); my %Attr_Name_Env; my %Attr_Remote; use T4::MyMod { Href_Attr_Name_Env => \%Attr_Name_Env, Href_Attr_Remote => \%Attr_Remote, }, qw( t_set_remote t_hello_world ) ; ## Main ## ## 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; ## Create object attributes # $Attr_Name_Env{ $idx_self } = $name_env; return $obj_new; } ## END Constructor 'new' ## DESTROY # sub DESTROY { my $self = shift; my $idx_self = ident $self; delete $Attr_Name_Env{ $idx_self }; print STDERR qq(\nDestroyed ) . __PACKAGE__ . qq( object $idx_self +\n\n); } ## END DESTROY sub t_get_name_env { my $self = shift; my $idx_self = ident $self; return $Attr_Name_Env{ $idx_self }; } sub t_get_remote { my $self = shift; my $idx_self = ident $self; return $Attr_Remote{ $idx_self }; } ## End Main ## 1;

      The File with Additional Subs

      package T4::MyMod; use base q(Exporter); @EXPORT = qw( t_hello_world t_set_remote ); # use Data::Dumper qw(Dumper); my $Href_Attr_Name_Env; my $Href_Attr_Remote; sub import { # print STDERR Dumper(@_); my $pkg = shift; my $href_attr = shift; $Href_Attr_Name_Env = $href_attr->{Href_Attr_Name_Env}; $Href_Attr_Remote = $href_attr->{Href_Attr_Remote}; T4::MyMod->export_to_level(1, $pkg, @_); } sub t_hello_world { my $self = shift; my $idx_self = ident $self; return qq(Hello World $idx_self); } sub t_set_remote { my $self = shift; my $arg = shift; my $idx_self = ident $self; $Href_Attr_Remote->{ $idx_self } = $arg; return 1; } 1;
Re: Import module code from external files?
by pemungkah (Priest) on Feb 17, 2010 at 21:10 UTC
    Hm. If I'm understanding you, you want to be able to arbitrarily extend a base module at compile time.

    Module::Pluggable, loading the plugins in a BEGIN block, perhaps? If your big module is easily divided into independent pieces, this would probably do the job.

    You might look at App::Cmd for an example of this. It lets you build a CLI from a base class plus appropriately-named plugins.

      Thank you Pemungkah. I took a look at Pluggable, and it seems interesting. I may return to it if the approach I detailed above doesn't cut it.