Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

How not to use "our"?

by Anonymous Monk
on Nov 29, 2010 at 12:11 UTC ( [id://874232]=perlquestion: print w/replies, xml ) Need Help??

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

Dear monks,

I've a module that stores common variables shared by other modules. Something like this:

package A; use strict; use Exporter qw(import); our @EXPORT = qw($maxlengths); our $maxlengths = { tinytext => 100, text => 200, mediumtext => 500, longtext => 2000 }; # and several others like this 1; package B; use strict; use A qw($maxlengths); # contrived sub check_form { my $self = shift; if (length($self->{address}) > $maxlengths->{tinytext}) { # code to trigger error } } 1;

Assume that, besides Module B, other modules use the global variable $maxlengths exported by Module A.

I've read in a number of places that we rarely have to declare variables with "our" and it's usually a bad idea.

How can I improve my code to achieve a similar behaviour i.e. to be able access a common variable but without resorting to globals?

Looking forward to your advice.

Replies are listed 'Best First'.
Re: How not to use "our"?
by JavaFan (Canon) on Nov 29, 2010 at 12:57 UTC
    How can I improve my code to achieve a similar behaviour i.e. to be able access a common variable but without resorting to globals?
    You don't. A variable either is lexical, or it can be accessed from elsewhere, and hence it's a global.
    I've read in a number of places that we rarely have to declare variables with "our" and it's usually a bad idea.
    If you read statements like that without context, discard them. "A little knowledge is worse than no knowledge". If it did come with context, then read and remember the context as well.

    Your example is an excellent case for 'our'.

    What is, IMO, bad is your use of string literals. I would write it as:

    use strict; use Exporter qw(import); our @EXPORT = qw($tinytext $text $mediumtext $longtext); our $tinytext = 100; our $text = 200; our $mediumtext = 500; our $longtext = 2000;
    Then, if you mistype 'tinytext' in your main code, you get a compile time error, instead of a run time uninitialized error. Using string literals as hash keys in the way you're doing is like coding without lexical variables, and warnings turned off.

      A quick comment/addition to the first two paragraphs: in cases where the common values are constant (or near-constant), one alternative is to wrap those values in a class and provide accessors. This provides commonly available values without global.

      Peter (Guo) Pei

        You're just replacing global scalars with global subs.

        Except for slowing down your code and making it longer, that isn't much of an improvement.

      Maybe it's just me. I prefer to put them in a hash (or hash reference) - it's a bit like organising things into a cabinet.

      my $chapters = { one => 'Chapter 1: AAA', two => 'Chapter 2: BBB', three => 'Chapter 3: CCC', };

      The above is more organised than having 3 separate variables. Any thoughts from the monks?

        If you think about it, a package symbol table (stash) *is* a hash, so the individual package variables within a package who's only purpose is to hold constants or configuration parameters is already organised as you would like it.

        If you then avoid exporting the individual variables and use them as $config::one, $config::two etc. then you've got the best of both worlds.

        The variables are nicely organised, and you also get typo checking at compile time:

        >perl -c -wE"package x;our $fred=50;package main; print $x::fred,$x::d +erf" Name "x::derf" used only once: possible typo at -e line 1. -e syntax OK

        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        It's your code, so you can do whatever you want to do. But how is putting 3 things in a hash (so you have four things) more organized than 3 things in a stash (which is also a hash)? And if you want to actually export your hash, you need to stuff the hash inside your stash (ending with with five things). It's like the difference of three drawers in a cupboard vs three drawers in a cabinet in a cupboard.

        But the main problem I have is the same problem I have with neither using strict, nor warnings.

        use strict; use warnings; my $chapters = { one => 'Chapter 1: AAA', two => 'Chapter 2: BBB', three => 'Chapter 3: CCC', }; say "We start with $chapters->{noe}"; # Compiler neither gives an er +ror, nor a warning.
        vs
        package chapters; our $one = 'Chapter 1: AAA'; our $two = 'Chapter 2: BBB'; our $three = 'Chapter 3: CCC'; package main; use strict; use warnings; say "We start with $chapters::noe"; # Compile time error.
        You're free to not use strict or warnings, but I think they are sane things to use. And it doesn't make sense to put a "use strict;" in your code, and then use code that won't be checked by strict.
Re: How not to use "our"?
by Khen1950fx (Canon) on Nov 29, 2010 at 12:36 UTC

      Ah thanks for the link. The global variable he talks about is $total, whose value is subject to change. How about variables that contain constant values that are used by different modules? How do we make those variables common to modules that need them?

        Simply build a function that returns the variables. Define values inside a function to make them inaccessible from outside. Something like this :

        #!/usr/local/bin/perl use strict; use warnings; package MyData; sub getData { my $data = { 'test' => 'yes', 'key' => 'value', 'some' => 'thing', 'array' => [ 1, 2, 3 ], }; my $key = shift; defined $data->{$key} ? return $data->{$key} : return; } package main; foreach my $testcase (qw(test key some bork)) { my $value = MyData::getData($testcase); if ($value) { print "Data for $testcase : $value \n"; } else { print "No such data : $testcase .\n"; } }
Re: How not to use "our"?
by kcott (Archbishop) on Nov 29, 2010 at 15:21 UTC

    Here's an example of how to "access a common variable but without resorting to globals":

    use strict; use warnings; package A; { my $maxlengths = { tinytext => 100, longtext => 2000, }; sub max_tiny_text { return $maxlengths->{tinytext}; } sub max_long_text { return $maxlengths->{longtext}; } } package B; use base 'A'; print 'From B: ', A->max_tiny_text(), "\n"; print 'From B: ', A->max_long_text(), "\n"; package main; print 'From main: ', B->max_tiny_text(), "\n"; print 'From main: ', B->max_long_text(), "\n";

    The output from this is:

    From B: 100 From B: 2000 From main: 100 From main: 2000

    Here's a quick rundown of what I've done here and why.

    • I've removed Exporter. Take a look at the Selecting What To Export section in that documentation.
    • I've set up class methods (max_tiny_text() and max_long_text()) to return the data.
    • The class data ($maxlengths) is lexically scoped with my inside a block such that it is only visible to the class methods. If you attempt to access $maxlengths within package A but outside that block, you'll get a compilation error.

    I've aimed to keep the same general framework you presented. There are (as usual) more ways to do it. :-)

    -- Ken

      Just because the names max_tiny_text and max_long_text take the CODE slot of the typeglob instead of the SCALAR slot doesn't make them any less global.
      I've removed Exporter. Take a look at the Selecting What To Export section in that documentation.
      And instead you write package name, and sub name to get a value. If you're willing to do that, you can do that with scalars as well, and not need Exporter:
      package Max; our $tiny_text = 200; our $long_text = 1000; package main; print "From main: $Max::tiny_text\n"; print "From main: $Max::long_text\n";
      You can even interpolate that way.

      Thanks, kcott!

      Wished I had read it in greater detail each time I went to that page, because I missed out that part entirely.

      But I'm a little puzzled.

      Under "Selecting What To Export", the first rule is "Do not export method names!" Why is that so? I thought one of the niceties about modules is that you can put functions in them so that they (the functions) can be reused? And how does one reuse the functions if they aren't exported. Hm...scratches head.

      Thanks to all who have commented, particularly wazoox with the working code!

        Very briefly, methods are functions used in object oriented code (there's OO tutorials in perldoc is you're not familiar with this). Access to methods should be through inheritance - exporting method names will likely break inheritance.

        If you read a bit further down that section you'll see it makes a distinction between modules that are object oriented and those that are just a collection of functions. As an example of the latter type, Scalar::Util is a collection of functions which you can selectively choose to export.

        -- Ken

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://874232]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (5)
As of 2024-03-28 22:10 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found