Exporter-Declare, exporting for modern Perl.
Exporting can be achieved through several routes. You can do it manually through typeglob manipulation, or you can use one of several modules that do it for you. One of the most proliferated, and common is the Exporter.pm module. More recently there have been some advances in the form of Sub::Exporter and similar improved exporting tools.
Exporter.pm is good in that it has a simple interface, is found in any perl installation, and is proven to get the job done. On the other hand it shows it's age; You specify exports in a package variable, and it can be difficult to work in custom import() logic.
Sub::Exporter is a much more customizable tool. You specify exports when you import the Sub::Exporter module. You also have many new features such as generated subs. When you import a module that uses Sub::Exporter you have many options, and ways to customize the imports. However the problem with Sub::Exporter is it's lack of introspection. After you define your exports you have no easy way to query the module for it's export list. You also have no way to modify the exporting after it is defined.
A modern exporting tool does not simply need to abandon the old practices of using a package variable. Providing additional exporting functionality such as generated subs is also insufficient. Modern perl tools such as Moose have shown the power of extensibility, and meta-driven solutions. The declarative interface, also proliferated due to Moose is showing its power as a useful interface. Enter Exporter::Declare...
Exporter::Declare was designed with modern perl in mind. It learns from the pioneering done by Sub::Exporter, but goes a step further by providing a declarative interface, and complete meta-driven introspection. Once you use Exporter::Declare you have multiple ways to declare an export. There are also several hooks into the import() method.
First an example of the interface:
package My::Exporter; use Exporter::Declare;default_exports qw/ exportA exportB ... /;
exports qw/ optionalA optionalB ... /;# Export a sub that isn't in this packages symbol table
export hello => sub { "hello" };my $iterator = 'a';
gen_export unique_class_id => sub {
my $current = $iterator++;
return sub { $current };
};sub after_import {
my $class = shift;
my ( $importer, $specs ) = @_;
...
}
This interface lets you spread out your export definitions, instead of requiring that they be clumped together in a use statement. You can also use them in loops, or other functions that simplify your job. You also have the after_import() method so that you do not have to think about how to wrap and/or call Exporter::Declare's import() from within your own.
Importing
Exporter::Declare provides an interface that is compatible with both Exporter.pm and Sub::Exporter. Some extra functionality has been added, but that is beyond the scope of this post.
Under the hood:
Under the hood every class that uses Exporter::Declare has a meta object associated with it. Whenever you declare an export it is added to the meta class. This prevents unwanted supporting items from cluttering your package. It also allows for a lower level interface into the exporting. You can get a list of all exports, modify the list of exports, and even create new tools to manipulate the meta data to your needs.
In addition, all exports are tracked as objects. When you export a sub or variable a reference to it is blessed as an Exporter::Declare::Export. This export class encapsulates some querying methods, as well as methods for injecting the export into other classes symbol tables.
This meta-driven framework allows for incredible extensibility, much like Moose has spawned many MooseX:: Extensions. It also allows for export inheritance. You can re-export the exports of other exporter's (both Exporter::Declare and Exporter.pm based, though Sub::Exporter does not provide enough introspection to allow for re-exporting it's exports in this way). This allows for Exporter::Declare extensions that re-export everything in Exporter::Declare in addition to their own tools.
I would strongly encourage anyone who writes packages that export to take a peek at Exporter::Declare. It's simple for simple exports, but has the power when you want to do more. Using it will also benefit anyone who wants to extend your exporting class later on. You might also take a peek at Exporter::Declare::Magic witch uses Devel::Declare to provide a much friendlier interface.
Why is specifying exports in a package variable a sign of age? Isn't using package variables for package-level interfaces quite appropriate there?
Package variables as an interface is not ideal because of the global nature of package variables, the lack of encapsulation, and the potential for conflicts. Use of a package meta-object with an accessor named after the feature, or import() arguments for use are a lot more controllable, an not dependent on exposing global data to any number of black boxes.