Introducing Exporter::Almighty

Consider a simple module like this:

  package MyApp::Util::Maths;
  
  use strict;
  use warnings;
  
  use constant PI    => 3.14159265358979;
  use constant EULER => 2.71828182845905;
  
  use base 'Exporter';
  
  our @EXPORT_OK = qw( PI EULER add );
  our %EXPORT_TAGS = (
    arithmetic => [ qw( add ) ],
    numbers    => [ qw( PI EULER ) ],
    all        => \@EXPORT_OK,
  );
  
  sub add {
    my ( $x, $y ) = @_;
    return $x + $y;
  }
  
  1;

You might use it like:

  use MyApp::Util::Maths qw( PI add );
  
  my $pi_plus_one = add( PI, 1 );

Exporter::Almighty is a module designed to reduce boilerplate in your utils-like modules, and increase their functionality.

The initial module can be rewritten as:

  package MyApp::Util::Maths;
  
  use Exporter::Almighty -setup => {
    const => {
      numbers => {
        PI    => 3.14159265358979,
        EULER => 2.71828182845905,
      },
    },
    tag => {
      arithmetic => [ 'add' ],
    },
  };
  
  sub add {
    my ( $x, $y ) = @_;
    return $x + $y;
  }
  
  1;

Exporter::Almighty sets up your exporting automatically (but using Exporter::Tiny instead of Exporter), and calls use strict and use warnings on your behalf.

Exporter::Almighty creates your constants for you, so you don't need to duplicate your list of constants anywhere.

A bonus for your caller is that they can do:

  use MyApp::Util::Maths qw( $PI $EULER );

To import read-only $PI and $EULER variables instead of traditional constants.

Your caller can also import things lexically:

  my $pi_plus_one = do {
    use MyApp::Util::Maths -lexical, qw( add $PI );
    add( $PI, 1 );
  };
  
  # add() and $PI are not defined outside the above block

Your caller can also rename any imported functions:

  use MyApp::Util::Maths 'add' => { -as => 'sum_of' };

Exporter::Almighty has integrations with Type::Tiny making it easy to define and export Type::Tiny type constraints as part of your module. For example:

  package MyApp::Util::Maths;
  
  use Exporter::Almighty -setup => {
    const => { ... },
    tag   => { ... },
    type  => { 'Types::Standard' => [ 'Int', 'Num' ] },
    class => [ 'Calc' => { class => 'MyApp::Calculator' } ],
  };
  
  ...;
  
  1;

Now people can import the Int and Num type constraints from your module:

  use MyApp::Util::Maths qw( Int );

They can even import a is_Int function:

  use MyApp::Util::Maths qw( is_Int );

You've also defined a Calc class type constraint which can be used like this:

  has calculator => (
    is        => 'ro',
    isa       => Calc,
    default   => sub { Calc->new },
  );

Exporter::Almighty makes defining enum-like data types easy:

  package My::Properties {
    use Exporter::Almighty -setup => {
      enum => { 'Status' => [ 'alive', 'dead', 'undead' ] },
    };
  }
  
  package Story::Character {
    use Moo;
    use My::Properties -lexical, '+Status';
    use experimental 'signatures';
    
    has status => (
      is       => 'ro',
      isa      => Status,
      default  => STATUS_ALIVE,
    );
    
    sub meet ( $self, $other ) {
      
      if ( $self->status eq STATUS_ALIVE
      and $other->status eq STATUS_UNDEAD ) {
        print "Help!\n";
      }
      
      return $self;
    }
  }

Next time you're writing a module that needs to export things, consider Exporter::Almighty. It could make things very easy for you, while adding a bunch of useful features for your caller.

Leave a comment

About Toby Inkster

user-pic I'm tobyink on CPAN, IRC and PerlMonks.