Bod has asked for the wisdom of the Perl Monks concerning the following question:

I've been looking at a question I asked 3 years ago in Refactoring webcode to use templates

How things have changed since then. We've closed down the part of the business that I refactored all the code for, but I certainly learnt a lot in the process.

One of the things I refactored and now do as standard is to have pretty much all common code in modules. Although they were common in my code until a few years back, I now never use the require keyword. This got me thinking...is require ever still required or is it obsolete in the modern world?

Replies are listed 'Best First'.
Re: Is require still required?
by hippo (Archbishop) on Jan 31, 2024 at 23:46 UTC

    I still frequently use require to load optional modules on-demand at run-time. There are other modules which can do the same but they are mostly wrappers around require. I don't see this usage pattern going away any time soon.


    🦛

Re: Is require still required?
by davido (Cardinal) on Feb 01, 2024 at 00:07 UTC

    I can think of a lot of examples of where a module should be required, at runtime, rather than used at compiletime. Factory method patterns, providing implementation of functionality into code based on some logic, only needing to load a module if a specific condition is met, dependency injection, and so on. And there are times that filenames versus bareword module names are useful. If you search CPAN for the word 'require' you'll find a ton of use cases you probably haven't considered, and while many of them don't HAVE to be coded the way they were, many of them used require because it was the appropriate tool for the job.


    Dave

Re: Is require still required?
by shmem (Chancellor) on Feb 01, 2024 at 05:23 UTC

    It is not obsolete, require is used. One use case:

    Usually there's a line reading

    1;
    as the last evaluated statement in a module file, since require demands a true value from an included file.

    As LanX pointed out, use is sort of a wrapper around require, but it doesn't return anything, it throws away the value returned by require. It is even a syntax error trying to assign from it:

    $result = use Some::Module; # illegal

    So you cannot assign from use, but you can from require. This is useful to load e.g. a config stash or getting back a data structure captured from Data::Dumper or Data::Dump into a file:

    # file hash.pl # anonymous hash reference { name => 'foo', data => [1,2,3], } __END__
    #!/usr/bin/perl # file program.pl use strict; use warnings; use Data::Dump; my $var = require './hash.pl'; dd $var; __END__

    Output:

    { data => [1, 2, 3], name => "foo" }

    Of course, you can do arbitrary computations in the required file.

    If you just want the last computed value from a file without having to declare an our variable in both source and target, or using Exporter, this is the way to do it - if you expect a true value to be returned. Otherwise, you would do FILE.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
      > require demands a true value from an included file.

      Note that it might soon no longer demand it.

      map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
        Except it's a really unreliable way to return a value.

        This is an assertion without argument.

        If it would be unreliable, it should fail sometimes - but it doesn't, only in the ways require does.

        Yes, the example code is just bare-bone without any checks. It is also nonsensical because it doesn't do anything useful.

        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: Is require still required?
by LanX (Saint) on Feb 01, 2024 at 02:09 UTC
    use means literally

    • BEGIN { require Module; Module->import( LIST ); }

    But there are many patterns where you explicitly don't want BEGIN or import or both to happen too.

    Similar for do and eval

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    see Wikisyntax for the Monastery

Re: Is require still required?
by uzb-dev (Acolyte) on Feb 06, 2024 at 01:40 UTC
    At $WORK, we still use Sybase ASE, a proprietary database system which has ... interesting ... requirements (heh!) on how to connect to it. It comes with its own SDK and libraries, which can't be linked statically. On MySQL/MariaDB/PostgreSQL, it's just a yum/apt/$distro_tool install away to install client libraries and you're off to the races. Even Microsoft SQL Server has a simple RPM file you can install to get at an ODBC driver that doesn't need anything more to connect to their database server. Not so with Sybase ASE, as it needs a bunch of environment variables set up before their libraries will actually work. Of course, for daily use, these are set in the shell's ~/.profile, but your ~/.profile, ~/.bash_profile, ~/.bashrc, ~/.zshrc (insert your favorite shell initialization dotfile here) is not sourced when scripts get run from cron, systemd timers or some other job scheduling framework that just fork out to a binary directly.

    So, most of our Perl scripts that touch Sybase ASE start with something like:

    # We need the $SYBASE and $LD_LIBRARY_PATH environment variables to be + able # to use the Sybase::Simple library (because it links to libsybct.so i +n # $SYBASE/$SYBASE_OCS/lib), but if we are invoked through cron, then # ~/.profile doesn't get sourced and we are stuck with a nearly empty +%ENV # hash. So if we detect $SYBASE is not in %ENV, reexec through a shel +l that # first sources in the necessary environment variables. unless (exists $ENV{SYBASE}) { # $^X is the path to the current perl interpreter # $0 is the script itself exec '/bin/sh', '-c', ". /db/sybsdk/SYBASE.sh ; exec $^X $0 @ARGV" +; } # even with the above, we need to wrap the import of the Sybase Perl # module in a runtime block as to avoid initialization by the Perl com +piler # prior to runtime (which checks for the necessary $SYBASE env variabl +es). # See 'perldoc perlfaq8' as to the difference between 'use' and 'requi +re'. # Basically: 'use' is ran at compile time, 'require' at runtime. require Sybase::Simple;
    So, to this day, there most definitely is a use (heh!) for require over use.
        Thanks for the input and the clever alternative of forking a subshell, dotsourcing any shellscripts in said subshell that modify the environment, and then serializing over the %ENV hash and eval'ing it in the parent process. This works for most normal environment variables, but not the ones that these Sybase libraries need. In particular, the Sybase libraries need custom LD_LIBRARY_PATH environment settings. LD_LIBRARY_PATH is interpreted by the OS's ld.so dynamic linker, but only at process initialization. Setting LD_LIBRARY_PATH from within a process will not affect the library search path of the process itself, only for subsequent childs the process launches. To expound on the problem, see the following transcript (env -i clears the environment):
        $ env -i PERL5LIB=.local/lib/perl5 perl -MSybase::Simple Can't load '.local/lib/perl5/x86_64-linux-thread-multi/auto/Sybase/CTl +ib/CTlib.so' for module Sybase::CTlib: libsybct_r64.so: cannot open s +hared object file: No such file or directory at /usr/lib64/perl5/Dyna +Loader.pm line 193. at .local/lib/perl5/Sybase/Simple.pm line 19. Compilation failed in require at .local/lib/perl5/Sybase/Simple.pm lin +e 19. BEGIN failed--compilation aborted at .local/lib/perl5/Sybase/Simple.pm + line 19. Compilation failed in require. BEGIN failed--compilation aborted. $
        The PERL5LIB environment variable is the only one that is set in the empty environment that launches the perl binary (to find the Sybase::Simple module). And indeed, in an empty environment, the XS .so blob that implements the actual database access layer in Sybase::CTlib (on which Sybase::Simple depends) refers to libraries that can't be resolved (and this is how these Sybase libraries work by design):
        $ env -i ldd .local/lib/perl5/x86_64-linux-thread-multi/auto/Sybase/CT +lib/CTlib.so linux-vdso.so.1 (0x00007ffcebdce000) libsybct_r64.so => not found libsybcs_r64.so => not found libsybblk_r64.so => not found libperl.so.5.32 => /lib64/libperl.so.5.32 (0x00007f4970c00000) libc.so.6 => /lib64/libc.so.6 (0x00007f4970800000) libm.so.6 => /lib64/libm.so.6 (0x00007f4970b25000) libcrypt.so.2 => /lib64/libcrypt.so.2 (0x00007f4970fc3000) /lib64/ld-linux-x86-64.so.2 (0x00007f4971032000) $
        The code I arrived at from your linked comment to 'steal' the environment from a subshell (with the added requirement of merging with any existing %ENV values), is:
        #!/usr/bin/perl BEGIN { my $shellcode = <<__KOEKOEK__; . sybsdk/SYBASE.sh perl -MData::Dumper -e 'print Dumper [\%ENV]' __KOEKOEK__ %ENV = (%ENV => @{eval qx{/bin/sh -c "$shellcode"}}); }; print $ENV{LD_LIBRARY_PATH}; require Sybase::Simple;
        But this still fails (even though the environment is modified correctly (the first line is the content of $ENV{LD_LIBRARY_PATH}):
        $ env -i PERL5LIB=.local/lib/perl5 perl ./do_shell_source.pl ~/sybsdk/DataAccess64/ODBC/lib:~/sybsdk/DataAccess/ODBC/lib:~/sybsdk/O +CS-20_0/lib:~/sybsdk/OCS-20_0/lib3p64:~/sybsdk/OCS-20_0/lib3p: Can't load '.local/lib/perl5/x86_64-linux-thread-multi/auto/Sybase/CTl +ib/CTlib.so' for module Sybase::CTlib: libsybct_r64.so: cannot open s +hared object file: No such file or directory at /usr/lib64/perl5/Dyna +Loader.pm line 193. at .local/lib/perl5/Sybase/Simple.pm line 19. Compilation failed in require at .local/lib/perl5/Sybase/Simple.pm lin +e 19. BEGIN failed--compilation aborted at .local/lib/perl5/Sybase/Simple.pm + line 19. Compilation failed in require at ./do_shell_source.pl line 14. $
        If we modify the script to just dump the %ENV hash, we see that all environment variables were indeed set correctly in the parent process from our detour in the qx{} subshell:
        #!/usr/bin/perl BEGIN { my $shellcode = <<__KOEKOEK__; . sybsdk/SYBASE.sh perl -MData::Dumper -e 'print Dumper [\%ENV]' __KOEKOEK__ %ENV = (%ENV => @{eval qx{/bin/sh -c "$shellcode"}}); }; use DDP; p %ENV;
        $ env -i PERL5LIB=.local/lib/perl5 perl ./do_shell_source.pl { _ "/usr/bin/perl", INCLUDE "~/sybsdk/OCS-20_0/include:", LD_LIBRARY_PATH "~/sybsdk/DataAccess64/ODBC/lib:~/sybsdk/DataAcc +ess/ODBC/lib:~/sybsdk/OCS-20_0/lib:~/sybsdk/OCS-20_0/lib3p64:~/sybsdk +/OCS-20_0/lib3p:", LIB "~/sybsdk/OCS-20_0/lib:", PATH "~/sybsdk/tools/bin:~/sybsdk/OCS-20_0/bin:/usr/l +ocal/bin:/usr/bin", PERL5LIB ".local/lib/perl5", PWD "/tmp", SAP_JRE8 "~/sybsdk/shared/SAPJRE-8_1_062_64BIT", SAP_JRE8_64 "~/sybsdk/shared/SAPJRE-8_1_062_64BIT", SHLVL 0, SYBASE "~/sybsdk", SYBASE_OCS "OCS-20_0", SYBROOT "~/sybsdk" } $
        And this corresponds to what's inside the (Sybase provided) shell script file for their proprietary libraries that is meant to be sourced in the shell's environment:
        $ cat sybsdk/SYBASE.sh # # SAP Product Environment variables # SAP_JRE8="~/sybsdk/shared/SAPJRE-8_1_062_64BIT" export SAP_JRE8 SAP_JRE8_64="~/sybsdk/shared/SAPJRE-8_1_062_64BIT" export SAP_JRE8_64 SYBASE_OCS="OCS-20_0" export SYBASE_OCS INCLUDE="~/sybsdk/OCS-20_0/include":$INCLUDE export INCLUDE LIB="~/sybsdk/OCS-20_0/lib":$LIB export LIB PATH="~/sybsdk/OCS-20_0/bin":$PATH export PATH # # Replace lib, lib3p, and lib3p64 with devlib, devlib3p, and devlib3p6 +4 when debugging # LD_LIBRARY_PATH="~/sybsdk/OCS-20_0/lib:~/sybsdk/OCS-20_0/lib3p64:~/syb +sdk/OCS-20_0/lib3p":$LD_LIBRARY_PATH export LD_LIBRARY_PATH SYBASE="~/sybsdk" export SYBASE LD_LIBRARY_PATH="~/sybsdk/DataAccess/ODBC/lib":$LD_LIBRARY_PATH export LD_LIBRARY_PATH LD_LIBRARY_PATH="~/sybsdk/DataAccess64/ODBC/lib":$LD_LIBRARY_PATH export LD_LIBRARY_PATH SYBROOT="~/sybsdk" export SYBROOT PATH="~/sybsdk/tools/bin":$PATH export PATH $
        So, coming back around again: a re-exec is still required in this case (due to the semantics and interaction of LD_LIBRARY_PATH and the system's dynamic linker). Moreover, because a Perl script file is first compiled before it is executed, any statements that fire at compile time (like use) need to be deferred to runtime (using require) when LD_LIBRARY_PATH potentially needs adjustments. Otherwise, the use Sybase::Simple; statements gets triggered before the re-exec through a shell that first sets LD_LIBRARY_PATH, causing compilation to abort due to DynaLoader getting an error from the OS's ld.so when trying to resolve libraries a module's XS .so file is linked to.
Re: Is require still required?
by Danny (Chaplain) on Feb 12, 2024 at 18:44 UTC
    I use require to conditionally parse files in different formats containing the same information. I import functions of the same name from different modules that return the same data, but the the functions from a given module are specific to a given format. That way I don't have to use different function names in the code, ask a question before every function is called, or load unused baggage. There are probably better solutions than require, but it does seem useful for situations like this.