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

Hey, I am trying to make my module aware of version number that would not need to be hardcoded in the module but instead initialized in a form of env variable in the program that actually calls all other perl scripts that make use of this module:
$ENV{"VER"} = "3.0";
In the module I planned to give a life to a global variable on the top of the module and use it across all subs/methods as follows:
my $ver_dir = "/opt/SAT/" . $ENV{"VER"};
$ENV{"VER"} however is undef at this stage yet. I would need to actually use it in subs/methods wherever I need to resolve path. It does seem to work in this way although that would cause me some real pain. Other way I thought of was to declare $ver_dir, as global var, as follows:
my $ver_dir = set_ver_dir();
wherea set_root_dir would be:
sub set_ver_dir { my $path = $FindBin::Bin; $path =~ /\opt\/SAT\/(\d*\.\d*)\//; my $ver = $1; return "/opt/SAT" . $ver }
Though such trick for making global vars within a module a result of one of module's sub does not seem to work. Any suggestions? Mind, tha I would NOT like to have it hardcoded in the module. Nor use any symlinks such us /opt/SAT/live or /opt/SAT/current that would point to the correct version on the file system.

Thank you.

Replies are listed 'Best First'.
Re: dynamiclly assigning values to global variables in modules
by NetWallah (Canon) on Mar 28, 2014 at 02:53 UTC
    Since modules are loaded before your program executes, if you are setting %ENV in your program, by the time it gets set, the module is already loaded.

    You have several options:

    • Provide a method in your module such that your program can call it, and set the variable
    • Pass the parameter with the "use statement" i.e. "use mymodule qw/3.0/;" Then the 3.0 will be passed to the import method of your module.
      But you need to be aware that this again happens before your program executes, so dont pass anything that is not a constant and available at compile time.
    • Make an "our" variable in your module (our $VER), and explicitly set it in your program via $ModuleName::VER = "0.3" (not recommended)
    • I'm sure Other monks will jump in here and provide more.

            What is the sound of Perl? Is it not the sound of a wall that people have stopped banging their heads against?
                  -Larry Wall, 1992

Re: dynamiclly assigning values to global variables in modules
by CountZero (Bishop) on Mar 28, 2014 at 06:47 UTC
    Am I right in understanding that you want to save the version number of a module, outside of that module, in a program "that actually calls all other perl scripts that make use of this module"?

    I think that is a very bad idea. A module should be (as much as possible) self-contained and know its own version number. If somehow other scripts need to know the version number of a module, there should be a variable or method available in that module to provide the version number. Look at many modules on CPAN: they have their version number stored in a $VERSION variable.

    But perhaps I misunderstood you or perhaps you have a very good reason to externally save the version number, but then you will have to explain it more clearly.

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

    My blog: Imperial Deltronics
Re: dynamiclly assigning values to global variables in modules
by tobyink (Canon) on Mar 28, 2014 at 10:01 UTC

    Don't use global variables. Global variables are a symptom of a wider problem: global state.

    Instead of relying on modules/subs picking up data from some sort of global place, you should pass the data to the function as a parameter.

    # this is bad... # sub do_something { if ( $MyApp::VERSION < 1.0 ) { # a global do_something_simple(); } else { do_something_complex(); } } do_something(); # this is good... # sub do_something { my ($version) = @_; if ( $version < 1.0 ) { # a local variable do_something_simple(); } else { do_something_complex(); } } do_something(1.2);

    Eventually you may find yourself passing around quite a few bits of data like this (a version number, maybe a database handle too, and a filename for writing output):

    use v5.14; use warnings; use DBI (); package MyApp { sub do_something { my ($version, $dbh, $output_file) = @_; if ( $version < 1.0 ) { # a local variable do_something_simple($output_file); } else { do_something_complex($output_file, $dbh); } } sub do_something_simple { my ($output_file) = @_; open my $fh, '>', $output_file or die; print $fh "Hello world\n"; } sub do_something_complex { my ($output_file, $dbh) = @_; open my $fh, '>', $output_file or die; print $fh "Hello world\n"; printf $fh "There are %d rows in the table.\n", $dbh->do("SELECT 1 FROM my_table"); } } MyApp::do_something( 1.2, DBI->connect("dbi:SQLite:dbname=myapp.sqlite"), '/tmp/output.txt', );

    It can become cumbersome to pass them around from one function call to the next, and tricky to keep track of which functions need access to which data. That's where object-oriented programming becomes handy:

    use v5.14; use warnings; use DBI (); package MyApp { use Class::Tiny qw( version dbh output_file ); sub do_something { my $self = shift; if ( $self->version < 1.0 ) { # an object attribute $self->do_something_simple; } else { $self->do_something_complex; } } sub do_something_simple { my $self = shift; open my $fh, '>', $self->output_file or die; print $fh "Hello world\n"; close $fh; } sub do_something_complex { my $self = shift; open my $fh, '>', $self->output_file or die; print $fh "Hello world\n"; printf $fh "There are %d rows in the table.\n", $self->dbh->do("SELECT 1 FROM my_table"); close $fh; } } my $app = MyApp->new( version => 1.2, dbh => DBI->connect("dbi:SQLite:dbname=myapp.sqlite"), output_file => '/tmp/output.txt', ); $app->do_something;
    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
Re: dynamiclly assigning values to global variables in modules
by Anonymous Monk on Mar 28, 2014 at 05:54 UTC

    Any suggestions?

    version numbers based on %ENV don't make sense -- I would rethink that (:in other words stop it :)

Re: dynamiclly assigning values to global variables in modules
by Laurent_R (Canon) on Mar 28, 2014 at 07:22 UTC
    If I understand correctly, you really want to define a directory where your module is going to do some work (e.g. read or write some files), defined by the calling process. You could define your environment variable in the shell script calling your Perl program (this is assuming a Unix environment, but there are equivalent environment vars in other operating systrems). Another way is to have in your module an init procedure that will set a lexical global variable; this variable can then act as a closure variable that can be used by other subroutines in your module.
Re: dynamiclly assigning values to global variables in modules
by Anonymous Monk on Mar 28, 2014 at 09:09 UTC
    This smells a bit like an XY Problem - what is the original problem that led you to the requirement of setting the version number dynamically? Also, it is not clear which version number you are talking about, is it the version number of the module (in which case setting it dynamically is usually not a good idea unless you can explain exactly why you need to), or are you talking about some other version number?
Re: dynamiclly assigning values to global variables in modules
by locked_user sundialsvc4 (Abbot) on Mar 28, 2014 at 12:50 UTC

    I strongly agree with tobyink here.   The design-issue here is not so much “that global value,” nor how or when it is represented or set, but rather, what and where you do something with it.   Every “what” that you do with it, should be in exactly one “where.”   And that should be ... a set of subroutines, located in just one place, which accept parameters, validate those parameters, and then return a finished work-product (which by-the-by uses this globally-assigned value).

    It is also good to “future-proof” your design, writing apparently-redundant subroutines that presently return the same value, but which are named and called based on the meaning of whatever it is that they do.   Two different meanings – use cases – might return identical values today, but they might not do so tomorrow.   Anticipate this early.

    The real problem with “global values” is that the logic which actually does something with those values is now scattered to the four winds.   You wouldn’t believe how hard it can be just to move an ill-designed application to a new location having a slightly different subdirectory structure ... because, since the logic to do it is everywhere, therefore dependencies are also everywhere.

    In your app, what exactly is $ver_dir for?   I presume that the application will use this to obtain the name of some files, given the file name.   Therefore, write a single routine that does that ... and that checks the name for accuracy, and maybe that checks that the file exists, and so on.   Whatever it is that you do with $ver_dir, do it right here in one module and do it suspiciously:   let it be what will detect that bug.   Write a separate routine for each significant use-case even if each does the same thing.   I can confidently cut many thousands of dollars from the analysis-phase of a legacy project that we’re inheriting, when I see coding-practices like that (instead of what I usually see).