55

I have Perl code which relies on Term::ReadKey to get the terminal width. My installation is missing this module, so I want to provide a default if the module isn't present rather than throw an exception.

How can I conditionally use an optional module, without knowing ahead of time whether it is available.

# but only if the module is installed and exists
use Term::ReadKey;
...

How can I accomplish this?

user157251
  • 64,489
  • 38
  • 208
  • 350
dlamblin
  • 40,676
  • 19
  • 92
  • 127
  • 3
    In my opinion, either the title is wrong, or all the answers (except perhaps the one using Module::Load::Conditional, if check_install() is used) are wrong. The title asks how to check "if I have a Perl module *before using it*". All the answers use some variation of "detect errors with eval *while* requiring/loading/using it". – Peter V. Mørch May 12 '14 at 13:37
  • I consider loading and using distinct. You may not agree… – dlamblin Oct 01 '18 at 05:25
  • The Perl keyword [`use`](https://metacpan.org/pod/perlfunc#use-Module-VERSION-LIST) has a very specific meaning, and therefore I consider the verb "using" to have a similar specific meaning. I'm not sure what "loading" refers to... So yes, I guess we'll agree to disagree ;-) – Peter V. Mørch Oct 02 '18 at 06:28
  • @PeterV.Mørch I agree, based on the chosen answer. See that title. Also trimmed up the question a bit. And provided a new answer https://stackoverflow.com/a/64922599/124486 – user157251 Nov 20 '20 at 01:54
  • 1
    @EvanCarroll your answer's good, though it seems functionally the same as the selected answer. Was changing the question necessary though? If I search Google for "how in Perl I can check if I have a module before using it", I'm unlikely to search instead for "how in Perl can one require a module optionally". And I think showing what from the module I was defaulting helped in getting serious answers instead of "what for?" comments. – dlamblin Nov 21 '20 at 01:20
  • @dlamblin that's not the question though. You're not checking before you use it. You could presumably do that too resolving the package name to the file name, and then using it. On the other hand, I can say for experience that I was looking for a question that asked what this title now reflects. I asked it and self-answered, and I had to close it as a dupe of this one when it was pointed out the question was already on the network. ;) – user157251 Nov 21 '20 at 03:09
  • The difference between the chosen answer, and my answer is that the `use` statement is a compile time check, so the op-tree doesn't have to be changed in runtime. The chosen answer though is doing the work in runtime. So no matter what all the code gets compiled (regardless of whether the module is present) and then in runtime if needed, it'll include the other module into the optree (an expensive runtime process). – user157251 Nov 21 '20 at 03:12
  • 1
    @EvanCarroll I understand that sometimes you have a question, and it gets closed as a duplicate of an existing question. Happens more often than it should. That doesn't mean my question "is not the question." You should take it up with reviewers, or add your answer here noting that its an alternate framing. – dlamblin Nov 27 '20 at 12:23

8 Answers8

97

Here's a bare-bones solution that does not require another module:

my $rc = eval
{
  require Term::ReadKey;
  Term::ReadKey->import();
  1;
};

if($rc)
{
  # Term::ReadKey loaded and imported successfully
  ...
}

Note that all the answers below (I hope they're below this one! :-) that use eval { use SomeModule } are wrong because use statements are evaluated at compile time, regardless of where in the code they appear. So if SomeModule is not available, the script will die immediately upon compiling.

(A string eval of a use statement will also work (eval 'use SomeModule';), but there's no sense parsing and compiling new code at runtime when the require/import pair does the same thing, and is syntax-checked at compile time to boot.)

Finally, note that my use of eval { ... } and $@ here is succinct for the purpose of this example. In real code, you should use something like Try::Tiny, or at least be aware of the issues it addresses.

Hasturkun
  • 32,476
  • 5
  • 67
  • 95
John Siracusa
  • 14,231
  • 6
  • 39
  • 53
  • D'oh, I should have thought of that first. +1 – ephemient Oct 30 '08 at 21:11
  • 1
    Yes that does really does work, and the semi-colon after the eval block is very important. – dlamblin Oct 30 '08 at 21:23
  • 10
    Avoid relying on $@ as much as possible. F.ex., some modules could set $@ as a side effect while being loaded without actually throwing an exception. The better option is to rely on the fact that `eval` will return undef when an exception was caught, ie. `if ( eval "use Term::ReadKey" ) { ... }`. – Aristotle Pagaltzis Nov 02 '08 at 15:31
  • Are you sure about that? perldoc -f eval says "If there was no error, $@ is guaranteed to be a null string." I just tried it the docs seem correct. – John Siracusa Feb 08 '09 at 04:33
  • 4
    If the module (or anything else) executes an eval after it generates an exception (such as, in a SIGDIE handler or in a DESTROY method), the value of $@ is replaced with the result of the latest eval. – daotoad Apr 29 '09 at 17:17
  • 1
    A tiny difference between `use Term::ReadKey;` and `eval { require Term::ReadKey };` in the answer is that now `Term::ReadKey` won't be available in `BEGIN` blocks. But that isn't a problem in the OP's example. – Peter V. Mørch Oct 27 '11 at 09:39
  • This is not working here for 'Package::DeprecationManager' It requires some other argument? `perl -MPackage::DeprecationManager -e ';'` You must provide a hash reference -deprecations parameter when importing Package::DeprecationManager at -e line 0. – Jens Timmerman Apr 05 '13 at 08:47
  • You an always use a sentinel hook at the end of `@INC` as well. Then it doesn't matter when you use or require it, it won't hit your sentinel until it fails all the other loaders, and you'll know you can't load it. And then you can set a variable or a value in some table. – Axeman Apr 30 '15 at 17:14
  • @Eugen Konkov: I rolled back your edit, which should have been a comment. (Note that using `require` in that case is pointless, fails because such a file does not exist. If you just want to import from a module, do that.) – Hasturkun Sep 16 '15 at 13:24
11

Check out the CPAN module Module::Load::Conditional. It will do what you want.

brian d foy
  • 121,466
  • 31
  • 192
  • 551
m0j0
  • 2,642
  • 4
  • 24
  • 32
  • 6
    Of course, that only works if you have that one installed, too. Probably a better solution if you do. – tvanfosson Oct 30 '08 at 20:57
  • Yeah... I can't guarantee I have that one. And while Detect::Module is, it doesn't list all the modules in it's $installed->modules() returned list of module names. – dlamblin Oct 30 '08 at 21:03
7

The classic answer (dating back to Perl 4, at least, long before there was a 'use') was to 'require()' a module. This is executed as the script is run, rather than when compiled, and you can test for success or failure and react appropriately.

Jonathan Leffler
  • 666,971
  • 126
  • 813
  • 1,185
4

And if you require a specific version of the module:

my $GOT_READKEY;
BEGIN {
    eval {
        require Term::ReadKey;
        Term::ReadKey->import();
        $GOT_READKEY = 1 if $Term::ReadKey::VERSION >= 2.30;
    };
}


# elsewhere in the code
if ($GOT_READKEY) {
    # ...
}
Hinrik
  • 783
  • 9
  • 14
4
if  (eval {require Term::ReadKey;1;} ne 1) {
# if module can't load
} else {
Term::ReadKey->import();
}

or

if  (eval {require Term::ReadKey;1;}) {
#module loaded
Term::ReadKey->import();
}

Note: the 1; only executes if require Term::... loaded properly.

Jason Plank
  • 2,322
  • 4
  • 29
  • 39
3
use Module::Load::Conditional qw(check_install);

use if check_install(module => 'Clipboard') != undef, 'Clipboard'; # class methods: paste, copy

using if pragma and Module::Load::Conditional core module.

check_install returns hashref or undef.


this module is also mentioned in the see also section of the pragma's documentation:

Module::Load::Conditional provides a number of functions you can use to query what modules are available, and then load one or more of them at runtime.

rwp
  • 31
  • 4
  • i really wish there were an easier and less noisy way of do that with a variant of the builtin `use` directive. – rwp Jan 12 '21 at 08:07
0

This is an effective idiom for loading an optional module (so long as you're not using it in a code base with sigdie handler),

use constant HAS_MODULE => defined eval { require Module };

This will require the module if available, and store the status in a constant.

You can use this like,

use constant HAS_READLINE => defined eval { require Term::ReadKey };

my $width = 80;
if ( HAS_READLINE ) {
  $width = # ... code, override default.
}

Note, if you need to import it and bring in the symbols you can easily do that too. You can follow it up.

use constant HAS_READLINE => defined eval { require Term::ReadKey };
Term::ReadKey->import if HAS_READLINE;

This method uses constants. This has the advantage that if you don't have this module the dead code paths are purged from the optree.

user157251
  • 64,489
  • 38
  • 208
  • 350
-1

I think it doesn't work when using variables. Please check this link which explains how it can be used with variable

$class = 'Foo::Bar';
        require $class;       # $class is not a bareword
    #or
        require "Foo::Bar";   # not a bareword because of the ""

The require function will look for the "Foo::Bar" file in the @INC array and will complain about not finding "Foo::Bar" there. In this case you can do:

 eval "require $class";
Utkarsh Kumar
  • 385
  • 3
  • 15