BryKKan
BryKKan

Reputation: 468

Require Perl Modules using an Aliased Name

***The following is background to help explain what I've tried so far. If you'd prefer to read the main question first, skip to the bottom.***


Starting Out

My Baz module invokes a number of other modules, all similar, each of which is one level down in the namespace. The ones of interest here compose the Thing role. In addition to the individual require statements, the constant list ALL_THINGS enumerates the relevant Thing modules for use later on. My original code looks like so:

package Foo::Bar::Baz;

use constant ALL_THINGS => qw{ Foo::Bar::Baz::ThingA Foo::Bar::Baz::ThingB ... };

require Foo::Bar::Baz::ThingA;
require Foo::Bar::Baz::ThingB;
[...]

Eliminating Redundancy

As I mentioned, there are quite a lot of Thing modules, and I'm still adding more. Each time I create a new Thing class, I have to add a new require statement and also add the same identical text to the ALL_THINGS list. In order to avoid this duplication, I wanted to replace the individual require lines with a loop iterating over ALL_THINGS. I added this, which works fine by itself:

foreach my $module (ALL_THINGS) {
    eval "require $module";
}

However this solution doesn't seem to play well with my next change.


Improving Readability

The full module name for each Thing is long and unwieldy. I'd like to alias the package name to make it easier to type/read. I looked at Package::Alias, but it seems that will use them, which I'd like to avoid if possible. The best solution I've come to so far is the pattern suggested in this question:

BEGIN { *Things:: = *Foo::Bar::Baz:: ; }

This also works, in the sense that it allows me to use Thing::ThingA->classMethod. However, unsurprisingly, it doesn't work in the require loop above, as require Thing::ThingA searches @INC for Thing/ThingA.pm rather than Foo/Bar/Baz/ThingA.pm.


Main Question: Putting Them Together

I'd like to cut down the long package names (i.e. Foo::Bar::Baz::ThingA) in my ALL_THINGS list to Things::ThingA, but still be able to use that same list to build my require statements in a loop.

Bonus Questions (related to eval "require $x"):


Note: I accepted Dave Sherohman's answer, as it most fully addresses the question I asked. However, I ultimately implemented a solution based on lordadmira's answer.

Upvotes: 6

Views: 874

Answers (5)

lordadmira
lordadmira

Reputation: 1832

NAME and PACKAGE are there mainly so that you can find out what package and name a reference came from. Other than that is a way to return the variable name rather than the variable value for e.g. debugging.

printf "%s\n", __PACKAGE__; my $y = \*ydp; pp *$y{PACKAGE}, *$y{NAME};
W
("W", "ydp")

Upvotes: 0

lordadmira
lordadmira

Reputation: 1832

Messing with typeglobs is like nuclear overkill here. Module::Runtime is the standard way to load modules at runtime based on configuration data. At this point everything can be ordinary variables. There is no benefit in using a constant here.

Here is my suggestion as from our IRC chat.

package Foo::Bar::Baz;

use strict;
use Module::Runtime "require_module";
use List::Util "uniq";

my $prefix = "Things::LetterThings";
my %prop_module_map = (
   foo => [ qw{ThingC ThingF ThingY} ],
   bar => [ qw{ThingL ThingB} ],
   baz => [ qw{ThingA ThingB ThingC ThingE ThingG ThingH ThingZ} ],
   # or
   # ALL => [ qw{ThingA .. ThingZ} ],
);
my @all_modules = uniq map { @$_ } values %prop_module_map;

sub load_modules {
  my $self = shift;

  # map module list if matching property found, otherwise use ALL_MODULES
  my $modules = $prop_module_map{$self->prop} ?
     $prop_module_map{$self->prop} :
     \@all_modules;

  #only do the map operation on the list we actually need to use
  my @modules = map { join "::", $prefix, $_  } @$modules;

  foreach my $module (@modules) {
    require_module($module);
  }
}

1;
__END__

Upvotes: 2

BryKKan
BryKKan

Reputation: 468

I came up with a solution based on an example in perlmod that seems to work, though I'm still trying to wrap my head around it. I'm posting it with the hope that someone can improve upon it, or at least explain it/provide feedback.

foreach my $package (ALL_THINGS) {
    no strict "refs";
    $package = *{$package}{PACKAGE}."::".*{$package}{NAME};
    eval "require $package";
}

Edit: After following the link over to perlref, I found this blurb in section (7):

*foo{NAME} and *foo{PACKAGE} are the exception, in that they return strings, rather than references. These return the package and name of the typeglob itself, rather than one that has been assigned to it. So, after *foo=*Foo::bar, *foo will become "*Foo::bar" when used as a string, but *foo{PACKAGE} and *foo{NAME} will continue to produce "main" and "foo", respectively.

From that, it makes sense that *Things{PACKAGE} will always resolve to Foo::Bar::Baz, since that's the package we're working in, and therefore the package that the typeglob "belongs to". In my code above, $package resolves to Things::ThingA, not Things, so we get *Things::ThingA{PACKAGE}. Similarly, the second part becomes *Things::ThingA{NAME}. I could hazard a few guesses as to why that might be working, but the truth is I'm not sure.

Upvotes: 0

Dave Sherohman
Dave Sherohman

Reputation: 46225

How black do you like your magic?

We all know that, in order to require modules, Perl looks through @INC to find the file it wants to load. One of the little-known (and even-less-used) aspects of this process is that @INC isn't limited to only contain filesystem paths. You can also put coderefs there, allowing you to hijack the module loading process and bend it to your will.

For the use case you've described, something like the following (untested) should do the trick:

BEGIN { unshift @INC, \&require_things }

sub require_things {
  my (undef, $filename) = @_;

  # Don't go into an infinite loop when you hit a non-Thing:: module!
  return unless $filename =~ /^Thing::/;

  $filename =~ s/^Thing::/Foo::Bar::Baz::/;
  require $filename;  
}

Basically what this does is, as the first entry in @INC, it looks at the name of the requested module and, if it starts with Thing::, it loads the corresponding Foo::Bar::Baz:: module instead. Simple and effective, but really easy to confuse future maintenance programmers (including yourself!) with, so use with caution.


As an alternate approach, you also have the option of specifying a package name in the module which doesn't correspond to the physical path of the file - the two are normally the same by convention, to make life easier when reading and maintaining the code, but there's no technical requirement for them to match. If the file ./lib/Foo/Bar/Baz/Xyzzy.pm contains

package Thing::Xyzzy;

sub frob { ... };

then you would use it by doing

require Foo::Bar::Baz::Xyzzy;
Thing::Xyzzy::frob();

and Perl will be perfectly happy with that (even though your coworkers may not be).


Finally, if you want to get rid of ALL_THINGS, take a look at Module::Pluggable. You give it a namespace, then it finds all available modules in that namespace and gives you a list of them. It can also be set to require each module as it is found:

use Module::Pluggable require => 1, search_path => ['Foo::Bar::Baz'];
my @plugins = plugins;

@plugins now contains a list of all Foo::Bar::Baz::* modules, and those modules have already been loaded with require. Or you can just call plugins without assigning the result to a variable if you only care about loading the modules and don't need a list of them.

Upvotes: 4

mob
mob

Reputation: 118665

Is there a different way to alias Foo::Bar::Baz:: as Things:: such that I can require Things::ThingA?

Yes. There are two requirements for this to work:

  1. Create the package alias as you have already done.

    BEGIN { *Things:: = *Foo::Bar::Baz:: }
    
  2. Create a symbolic link to mylibs/Things from your mylibs/Foo/Bar/Baz directory (where mylibs is the path to your Perl modules)

    (Make a link from the Foo/Bar/Baz.pm file to Things.pm, too, if you want)

Once you have done this, and call eval "require Things::Quux" or eval "use Things::Quux", Perl will load the file in mylibs/Things/Quux.pm, which is the same as the mylibs/Foo/Bar/Baz/Quux.pm file. That file has a package Foo::Bar::Baz::Quux statement in it, but since that package is already aliased to the Things::Quux namespace, all of its subroutines and package variables will be accessible in either namespace.

Is there some other generally accepted method of tying together packages at different levels of the same namespace to obviate the need for all this?

It's not clear what your object model is, but if *::Thing1, *::Thing2, etc. are all implementations of some common base class, you could consider a factory method in the base class.

package Foo::Bar::Baz;
sub newThing {
    my ($class, $implementation, @options) = @_;
    eval "use $class\::$implementation; 1"
        or die "No $implementation subclass yet";
    no strict 'refs';
    my $obj = "$class\::$implementation"->new(@options);
    return $obj;
}

Now Foo::Bar::Baz::Thing7 (which may or may not be aliased to Things::Thing7) will only be loaded if it is needed, say, from a call like

my $obj7 = Foo::Bar::Baz->newThing("Thing7",foo => 42);
print ref($obj7);   # probably  Foo::Bar::Baz::Thing7

Upvotes: 3

Related Questions