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
.