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)
Pingback: Perlbuzz news roundup for 2016-05-27 – perlbuzz.com