DEV Community

Toby Inkster
Toby Inkster

Posted on • Originally published at toby.ink on

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;
Enter fullscreen mode Exit fullscreen mode

You might use it like:

  use MyApp::Util::Maths qw( PI add );

  my $pi_plus_one = add( PI, 1 );
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 );
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Your caller can also rename any imported functions:

  use MyApp::Util::Maths 'add' => { -as => 'sum_of' };
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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

  use MyApp::Util::Maths qw( Int );
Enter fullscreen mode Exit fullscreen mode

They can even import a is_Int function:

  use MyApp::Util::Maths qw( is_Int );
Enter fullscreen mode Exit fullscreen mode

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 },
  );
Enter fullscreen mode Exit fullscreen mode

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;
    }
  }
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)