I've recently adopted a couple of Exporter modules, and might soon be adopting a third. This has prompted me to look into exporter modules a bit more, and now I'm working on a review of all such modules (list below). While doing this I've been playing around, creating a minimal exporter that does just what I need.
While working on reviews, I steadily learn more about the subject, and often have a go at writing a module, as that generally helps me understand the area.
When I create a module that provides subs,
I don't export any symbols by default.
My minimal exporter needs to provide an import
method,
which lets you import any subs from the module:
package Function::Exporter;
sub import {
my $class = shift;
my $package = caller;
no strict 'refs';
foreach my $subname (@_) {
*{"${package}::$subname"} = *{"${class}::$subname"}{CODE};
}
}
1;
This method will import the specified subs from the containing package
to the use'ing package. So a client of Function::Exporter
uses it
to import the import
method into itself. And then the same method
will import subs from that package.
Here's a simple module which provides one public function:
package Spells;
use Function::Exporter qw/ import /;
sub gnusto { print "gnusto!\n"; }
1;
And you can then use the Spells
module in the usual way:
use Spells qw/ gnusto /;
I only want public functions to be exportable. One way to achieve this is by using lexical subs (introduced in 5.18.0):
package Spells;
use Function::Exporter qw/ import /;
no warnings "experimental::lexical_subs";
use feature "lexical_subs";
my sub real_gnusto {
print "gnusto!\n";
}
sub gnusto {
real_gnusto();
}
1;
If you try and import real_gnusto
, you'll get the following run-time error:
Undefined subroutine &main::real_gnusto called at spell.pl line 5.
I think a compile-time error would be better though, so we change
the inside of the import()
loop to:
*{"${package}::$subname"} = *{"${class}::$subname"}{CODE}
// croak "No $subname() available for import from $class";
Lexical subs are a recent introduction and they're experimental, so they're not widely used. The convention for private subs is to start the name with an underscore, so we shouldn't let users import such functions:
foreach my $subname (@_) {
croak "can't import private function $subname()" if $subname =~ /^_/;
*{"${package}::$subname"} = *{"${class}::$subname"}{CODE}
// croak "No $subname() available for import from $class";
}
This doesn't provide an unimport()
method, but for a minimal
function exporter is that really needed?
I also wonder if a cleaner interface would be to let the user write:
package Spells;
use Function::Exporter;
sub gnusto { print "gnusto!\n"; }
1;
The Function::Exporter::import
method would be something like:
sub import {
my class = shift;
my $package = caller;
*{"${package}::import"} = sub {
# import function from above
};
}
A slightly cleaner public interface traded for messier internals / asymmetic semantics. Not sure.
Pragmatically speaking though, you really want an exporter to
be a core module, rather than adding a non-core dependency just for this.
It's currently such a small method that I'd almost consider just
pasting it into any module, to save a dependency.
Or I could create a Dist::Zilla plugin which adds the import()
method to your module.
I've only worked through a handful of exporter modules so far, and have thirty-something to go. I wonder how much this minimal module will change as a result of looking at all the others?
These are the exporter modules I'm aware of so far. Let me know if I've missed any.
Exporter, Attribute::Exporter, Badger::Exporter, Class::Exporter, Const::Exporter, Constant::Export::Lazy, Constant::Exporter, Export::Lexical, ExportAbove, ExportTo, Exporter::Auto, Exporter::AutoClean, Exporter::Cluster, Exporter::Constants, Exporter::Declare, Exporter::Easiest, Exporter::Easy, Exporter::Lexical, Exporter::LexicalVars, Exporter::Lite, Exporter::NoWork, Exporter::Proxy, Exporter::Renaming, Exporter::Shiny, Exporter::Simple, Exporter::Tidy, Exporter::Tiny, Exporter::VA, Exporter::WithBase, Import::Base, Import::Into, Lexical::Import, Moose::Exporter, MooseX::App::Exporter, Mouse::Exporter, No::Worries::Export, Panda::Export, Sub::Exporter, Sub::Exporter::Progressive, Sub::Exporter::Simple, Sub::Import, Xporter.
comments powered by Disqus