Day the Fourth: Badger::Exporter

Badger::Exporter

Top Close Open

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';

Tiered API

Top Close Open

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.

Inheritance

Top Close Open

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.

Fork Me on Github