Day the Fourth: Badger::Exporter
In yesterday's installment we looked at the Badger::Constants module. Today we'll look at how you can define your own constants module.
The traditional route for exporting symbols (constants, variables, functions, etc.) from one package to another is using the Exporter module. The Badger::Exporter module performs a similar role. However, the key difference is that Badger::Exporter understands the concept of inheritance (which Exporter doesn't). We'll see how that is useful later on.
So here's an example module defining some simple constants.
package Your::Constants;
use strict;
use warnings;
use base 'Badger::Exporter';
use constant {
MESSAGE => 'Hello World!',
VOLUME => 10,
TRUE => 1,
FALSE => 0,
};
our $EXPORT_ALL = 'MESSAGE';
our $EXPORT_ANY = 'TRUE FALSE VOLUME';
our $EXPORT_TAGS = { truth => 'TRUE FALSE' };
1;
If you've ever used the Exporter module then you'll find this example reassuringly
familiar (albeit slightly different). Instead of using Exporter as a base class we use Badger::Exporter, and instead of
defining @EXPORT, @EXPORT_OK and
%EXPORT_TAGS we use $EXPORT_ALL,
$EXPORT_ANY and $EXPORT_TAGS.
$EXPORT_ALL defines the symbols that will always be exported
from your module. $EXPORT_ANY is those that can be exported
if the caller specifically asks for them. $EXPORT_TAGS
defines groups of tags that can be exported in one go. In the above
example, we must ensure that any symbols defined in
$EXPORT_TAGS are also present in $EXPORT_ANY.
You can now use Your::Constants module to load constants
into your code, using either the individual symbol names or the name of
the tag set group.
# either use Your::Constants 'TRUE FALSE VOLUME'; # or use Your::Constants ':truth VOLUME';
Badger uses package variables
(like $EXPORT_ALL, $EXPORT_ANY, etc) as the
lowest common denominator to get the job done. However, getting and
setting package variables can be a little messy, especially if you're
trying to do it from a different package (as we saw yesterday).
Badger::Exporter also defines class methods that allow you declare your exports without having to worry about the underlying package variables.
__PACKAGE__->export_all('MESSAGE');
__PACKAGE__->export_any('VOLUME');
__PACKAGE__->export_tags( truth => 'TRUE FALSE' );
Another advantage of using a class methods is that we no longer need to
declare TRUE and FALSE via export_any(). The
export_tags() method is smart enough to do
that for us.
There is also the exports() method which allows you to set all of the above (and more) in one go.
__PACKAGE__->exports(
all => 'MESSAGE',
any => 'VOLUME',
tags => 'TRUE FALSE'
);
At an even higher level of abstraction, we can use Badger::Class to declare the exports for us. This is an example of metaprogramming. We'll be looking at Badger::Class in later instalments, so for now it's just a quick glimpse.
package Your::Constants;
use Badger::Class
constant => {
MESSAGE => 'Hello World!',
VOLUME => 10,
TRUE => 1,
FALSE => 0,
},
exports => {
all => 'MESSAGE',
any => 'VOLUME',
tags => { truth => 'TRUE FALSE' },
};
The Badger::Class module
defines a number of import hooks which perform different actions. The
constant hook tells Badger::Class to define some constants. The exports
hook is patched straight into the export() method in Badger::Exporter that we
were just looking at.
Notice that we don't need use strict or use
warnings any more because use Badger::Class
effectively does it for us (using a neat trick borrowed from Moose). Also note that we no longer need to
declare Badger::Exporter as a base class because
Badger::Class will also do that for us by virtue of the fact
that we declared some exports.
What makes Badger::Exporter different to Exporter is that it understands inheritance between object
classes. What this means in practice is that you can create a subclass of
Your::Constants (or indeed any module using Badger::Exporter) and it will
automatically export everything that its base class exports (or base
classes in the case of multiple inheritance).
Here's an example where we create a subclass of
Your::Constants that defines a new value for the
VOLUME constant and adds COLOUR. All the other
constants are inherited.
package Your::New::Constants;
use base 'Your::Constants';
use constant {
VOLUME => 11,
COLOUR => 'black',
};
our $EXPORT_ANY = 'VOLUME COLOUR';
Here's how we use it:
use Your::New::Constants 'VOLUME COLOUR :truth';
print TRUE; # 1
print FALSE; # 0
print "This amp goes up to ", VOLUME; # one louder
print "How much more ", COLOUR, # none, none more black
" could this be?";
We get the new values for VOLUME and COLOUR
and the inherited values for TRUE and
FALSE
For a final trick, here's an example showing how you can use multiple inheritance to create a constants module which aggregates the constants from two or more other modules.
package Your::Project::Constants;
use base qw( Your::Database::Constants
Your::Web::Constants
Your::Colour::Constants );
Now Your::Project::Constants inherits all constants (and any
other exportable items) defined by
Your::Database::Constants, Your::Web::Constants
and Your::Colour::Constants.