Reputation: 468
***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.***
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;
[...]
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.
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
.
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.
Foo::Bar::Baz::
as Things::
such that I can require Things::ThingA
?Things::ThingA
to Foo::Bar::Baz::ThingA
in (or before?) the eval so that require
finds the correct package?Bonus Questions (related to eval "require $x"
):
eval
? require
statements for each module)?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
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
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
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
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
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:
Create the package alias as you have already done.
BEGIN { *Things:: = *Foo::Bar::Baz:: }
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