According to Perl Critic we should "Export symbols via @EXPORT_OK or %EXPORT_TAGS instead of @EXPORT"
Following this advice I am transitioning my module to @EXPORT_OK from @EXPORT, but I am having difficulty with functionality around Exporter. Below I will give an illustrative example of the situation.
Original Code
Originally my code looked like this and it worked as expected:
lib/MyAnimals/Cats.pm
package MyAnimals::Cats;
use parent qw(Exporter);
our @EXPORT = qw(tabby calico abyssinian tiger panther leopard);
lib/MyAnimals/Dogs.pm
package MyAnimals::Dogs;
use parent qw(Exporter);
our @EXPORT = qw(dachshund poodle great_dane st_bernard);
lib/MyAnimals.pm
package MyAnimals;
$Exporter::Verbose = 0;
sub import {
my $self = shift;
my $caller = caller;
foreach my $package (@_) {
my $full_package = "MyAnimals::$package";
eval "require $full_package";
if ($@) {
warn "Could not require MyAnimals::$package: $@";
}
$full_package->Exporter::export($caller);
}
return;
}
=head2 How it works
The MyAnimals module simply imports functions from MyAnimals::* modules.
Each module defines a self-contained functions, and puts those function
names into @EXPORT. MyAnimals defines its own import function, but that
does not matter to the plug-in modules.
This function is taken from brian d foy's Test::Data module. Thanks brian!
This allowed me to have in my program:
program.pl
#!/usr/bin/env perl
use MyAnimals qw(Cats Dogs);
Which imported all of the cats and dogs (functions) into the program.
New Code
However, since it is considered bad form to pollute a user's namespace by exporting everything instead of just what they asked for, I am moving to this code:
lib/MyAnimals/Cats.pm
package MyAnimals::Cats;
use parent qw(Exporter);
our %EXPORT_TAGS = (house => [qw(tabby calico abyssinian)], jungle => [qw(tiger panther leopard)]);
Exporter::export_ok_tags('house'); # add tabby calico abyssinian to @EXPORT_OK
Exporter::export_ok_tags('jungle'); # add tiger panther leopard to @EXPORT_OK
# add all the other ":class" tags to the ":all" class, deleting duplicates
{
my %seen;
push @{$EXPORT_TAGS{all}},
grep {!$seen{$_}++} @{$EXPORT_TAGS{$_}} foreach keys %EXPORT_TAGS;
}
lib/MyAnimals/Dogs.pm
package MyAnimals::Dogs;
use parent qw(Exporter);
our @EXPORT = qw(dachshund poodle great_dane st_bernard);
our %EXPORT_TAGS = (small => [qw(dachshund poodle)], big => [qw(great_dane st_bernard)]);
Exporter::export_ok_tags('small'); # add dachshund poodle to @EXPORT_OK
Exporter::export_ok_tags('big'); # add great_dane st_bernard to @EXPORT_OK
# add all the other ":class" tags to the ":all" class, deleting duplicates
{
my %seen;
push @{$EXPORT_TAGS{all}},
grep {!$seen{$_}++} @{$EXPORT_TAGS{$_}} foreach keys %EXPORT_TAGS;
}
This now allows my program to import only the animals it wants:
program.pl
#!/usr/bin/env perl
use MyAnimals::Cats qw(:jungle tabby);
use MyAnimals::Dogs qw(:big);
Problem
However, I have lost the functionality of MyAnimals.pm, specifically I no longer have a simple, clean way of importing eveything in those cases where I really want to, say in a test for example:
t/01-no_longer_works.t
use MyAnimals qw(Cats Dogs);
Now I have to do this: t/02-works_but_is_not_elegant.t
use MyAnimals::Cats qw(:all);
use MyAnimals::Dogs qw(:all);
Question
What I am looking for is a way to change MyAnimals.pm so that the test t/01-no_longer_works.t functions as it previously did (when I was using @EXPORT) and imports all of the animals (functions).
First, don't inherit from Exporter (unless you are making a fancier and more specific exporter yourself). It's enough to get its
importroutine:Next, it looks like most of your problem is that you want to export from
MyAnimals::CatsthroughMyAnimalsto the final caller. That's something that I'd typically try to avoid. It's fine to play around with this sort of thing, but I'd hate to have to deal with that in production code.Having said that,
Exporterhas another routine,export_to_level, that allows you to export further up the chain. With this you can effectively skip thatMyAnimalsand export to whatever calledMyAnimals.Here's
MyAnimals, in which you have to define your ownimportand do a lot of work. Remember that you can't just ask every module to import everything because they only export their own stuff (although you can goof around by them having the same export tags I guess). I've left all that bookkeeping and fiddling out of this:Now,
MyAnimals::Cats, which is now a specialized importer so it inherits fromExporter. You can't get toexport_to_levelotherwise:You may also like to look at the source for
Modern::Perlto see how it imports a bunch of things from different sources.And then the test program, which only loads
MyAnimalsand asks for two things:The output shows that I got what I wanted:
But, this is not a good design if there's any other way you could get this to work. There are various things to recognize the modules that you want to load and then call their import routines with the
:alltag like you have shown. However, you are back to where you started where you are exporting everything anyway. If you are going to do that, just more complicated, save yourself the trouble by doing nothing at all.And remember, there is no such thing as a best practice with no context. Perl Best Practices was never meant to be hard and fast rules (read Damian's intro again). Do what makes sense for the problem at hand. There are many cases where I'd rather have default exports that I can turn off rather than things I have to enable every time:
Part of Perl's philosophy was to make the things you do all the time easy (less typing), while still giving you a way to do the things you do rarely. This gets lost for the people who want to make all code look alike no matter the intent of the code.