Loading module conditionally (and other options)

If you want to load a module conditionally, you can’t do this (it’s a syntax error):

use MyModule if $cond;

You can use require, but: 1) it’s run-time, not compile-time; 2) it doesn’t allow specifying minimum version or imports. So you have to do something like this:

BEGIN { if ($cond) { require MyModule; MyModule->VERSION(1.23); MyModule->import(@import_args) } }

Alternatively, you can use the if pragma (available in core since 5.6.2, a.k.a. forever):

use if $cond, "MyModule";
use if $cond, MyOtherMod => @import_args;

But: 1) you have to quote the module name (unless you use fat comma, like the second example); 2) you can’t specify minimum version; 3) you can’t do the equivalent of use MyModule () (avoiding calling import). And, the syntax is… well, suboptimal.

For completeness, you can also use eval "". The downsides: 1) you need to add or die to rethrow captured exception; 2) you need to express everything as a string, so if you import some data structure, you need to dump it as Perl first; 3) it’s slower (but most of the time it shouldn’t matter); 4) more importantly, you have to be more careful since it’s eval after all. So the usage of eval is not particularly attractive in this case.

use Data::Dump qw(dump);
if ($cond) { eval "use MyModule" . (@import_args ? ", ".dump(@import_args) : ""); die if $@ }

Let’s face it, use is special and its specialness causes an annoyance if you want to emulate or “extend” it.

I’m thought-experimenting with a swiss-army-knife pragma for loading modules called module (not yet written). You still have to use use to get the compile-time access, it’s more verbose, and you still need to quote the module name, but the syntax is more regular and it’s open to future extension.

# load a single module, default imports, equivalent to: use MyModule;
use module "MyModule";

# load several modules, equivalent to multiple 'use' statements
use module "MyModule", "MyOtherMod";

# specify imports, equivalent to: use MyModule 'one', 'two';
use module "MyModule", -import => ['one', 'two'];
use module "MyModule", -import => ('one', 'two'); # allow this too? to trap possibly common mistake

# don't import(), equivalent to: use MyModule ();
use module "MyModule", -noimport => 1; # ugh, too verbose?

# specify version (call VERSION())
use module "MyModule", -version => 1.23;

# conditional loading
use module "MyModule", "MyOtherMod", -if => $cond;

I find the last example slightly more readable compared to using the if pragma, because you get to specify the module names first (although you can also write: use module -if => $cond, "MyModule").

Other things I’m thinking of adding:

  • An option to delay loading the module until runtime (we already have Module::Load or Class::Load though);
  • An option to accept filenames instead of module names (like Module::Load);
  • Add awareness to Exporter (@EXPORT) and allow specifying regex/wildcard to pick imports.
  • More complex version specification (minimum/maximum, blacklist some versions, etc)

One thought on “Loading module conditionally (and other options)

  1. Pingback: Perlbuzz news roundup for 2016-05-27 – perlbuzz.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s