3

I am stuck while creating a perl Moose module.

I have a global pm module.

package XYZ;
require Exporter;
our @ISA = qw(Exporter);  ## EDIT missed this line 
our @EXPORT_OK = qw($VAR);
my $VAR1 = 1;
our $VAR = {'XYZ' => $VAR1};
1;

I want to get $VAR in a Moose module I'm creating

package THIS;
use Moose;
use YAML::XS;
sub get_all_blocks{
  my ($self) = @_;
  require $self->get_pkg(); # this returns the full path+name of the above package
  # i cannot use use lib+use since the get_pkg starts complaining 

  our $VAR;
  print YAML::XS::Dump($XYZ::VAR); # this works
  print YAML::XS::Dump($VAR); # this does not work
  # i cannot use the scope resolution since XYZ would keep changing.

}
1;

Can someone please help me with accessing variable?

EDIT: Missed one line in the package XYZ code. I cannot touch the package XYZ since it is owned/used by someone else, I can just use it :(

Philip Kirkbride
  • 21,381
  • 38
  • 125
  • 225
justrajdeep
  • 855
  • 3
  • 12
  • 29
  • 1
    [Don't export variables!](http://perldoc.perl.org/Exporter.html#What-Not-to-Export) – ThisSuitIsBlackNot Jan 26 '17 at 18:30
  • 3
    "Don't export variables" seconded, but then more generally -- why don't you have `use XYX qw(...)` in your `THIS`? You want symbols from other packages, so import them. – zdim Jan 26 '17 at 18:33
  • I did a one-char fix. In the package, you had `XYX`, changed it to `XYZ`. It looks like what you intended. If not, feel free to revert my change. – stevieb Jan 26 '17 at 18:33
  • "Don't export variables" - 3rded. Write a getter function/method. – stevieb Jan 26 '17 at 18:34
  • 1
    `$VAR` doesn't refer to `$XYZ::VAR` because 1) You never imported it. You're missing `use XYZ;` 2) It's being hidden by another variable. Remove `our $VAR;`. – ikegami Jan 26 '17 at 19:46
  • @zdim ... i have not tried out the answers, got sidetracked. Will chose the correct answer when i try them out. – justrajdeep Feb 09 '17 at 18:59
  • OK, thanks. No rush, this was just a reminder since people often forget and never come back. It is good to (eventually) have an answer labeled, for listings and future visitors to the page :) – zdim Feb 09 '17 at 19:35
  • You stated that you cannot touch the `package XYZ` because it's owned by somebody else. In that case directly using the fully qualified global `$XYZ::VAR` is the best you can hope for. Anything else will just over complicate the matter. If you're into Rube Goldberg you could always subclass XYZ or monkey patch it! – lordadmira Dec 19 '20 at 06:39

2 Answers2

5

Exporting variables may easily lead to trouble.

Why not

package XYZ;

use strict;
use warnings;

use Exporter qw(import);
our @EXPORT_OK = qw(get_var);

my $VAR = '...';  # no need for "our" now

sub get_var { return $VAR }
...
1;

and then

package THIS;

use warnings;
use strict;

use XYZ qw(get_var);

my $var = get_var();    
...
1;

See Exporter.

As for what you tried to do, there are two direct problems

  • $VAR from XYZ is never imported into THIS. If you need symbols from other packages you need to import them. Those packages have to make them available first, so you need to add it to @EXPORT_OK as well. Like above but with $VAR instead of get_var()

    package XYZ;
    ...
    use Exporter qw(import);
    our @EXPORT_OK = qw($VAR);
    
    our $VAR = '...';  # need be "our" for this
    

    with

    package THIS;
    ...
    use XYZ qw($VAR);
    
    print "$VAR\n";
    

    Now $VAR can be used directly, including being written to (unless declared constant); that can change its value under the feet of yet other code, which may never even know about any of it.

    Another way is to use @EXPORT and then those symbols are introduced into every program that says use Package;. I strongly recommend to only use @EXPORT_OK, when callers need to explicitly list what they want. That also nicely documents what is being used.

  • Even once you add that, there is still a variable with the same name in THIS, which hides (masks, shadows) the $XYZ::VAR. So remove our $VAR in THIS. This is an excellent example of one problem with globals. Once they're introduced we have to be careful about them always and everywhere.

But there are far greater problems with sharing variables across modules.

It makes code components entangled and the code gets harder and harder to work with. It runs contrary to principles of well defined scopes and modular design, it enables action at a distance, etc. Perl provides many good tools for structuring code and we rarely need globals and shared variables. It is telling that the Exporter itself warns against that.

Note how now my $VAR in XYZ is not visible outside XYZ; there is no way for any code outside XYZ to know about it or to access it. When it is our then any code in the interpreter can write it simply as $XYZ::VAR, and without even importing it; that's what we don't want.

Of course that there may be a need for or good use of exporting variables, what can occasionally be found in modules. That is an exception though, to be used sparingly and carefully.


Unless they're declared as package globals under a lexical alias via our in their package, in which case they can be used anywhere as $TheirPackageName::varname.

This complete privacy is courtesy of my.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • @user590222 I've added quite a bit since the original post, you may want to read it over. I don't know whether you saw the edits thus this note. – zdim Jan 27 '17 at 21:10
  • He said that he could not change package XYZ so he can't create a getter there. – lordadmira Dec 19 '20 at 06:32
  • @lordadmira That statement was added after the fact. Even so, this post _is_ what I want to convey to others who come here -- and, I solve their direct need as well (did you not notice that?) – zdim Dec 19 '20 at 06:45
  • I see what you're saying. Everything you wrote is good stuff. I just wanted to point out that the answer does not comport with the question because the current text is what people who come here will see. XYZ cannot be modified. – lordadmira Dec 19 '20 at 06:52
  • @lordadmira Well, alright; thanks for noting that. The way I see it is: people who come here generally don't have an "immutable" `XYZ` on their hands; so the main answer applies for most. But -- there's also a solution for `XYZ` as given, for exporting variables. So I'd rather not turn it around (I did just edit) – zdim Dec 19 '20 at 07:12
  • 1
    @lordadmira There are also a few loose ends in the question (I don't get `THIS` package?) -- so (I think) I was mostly trying to address a generic issue of how to send variables around. – zdim Dec 19 '20 at 07:16
2

You do not want our $VAR; in THIS's namespace. That creates a lexical reference to $THIS::VAR. Not what you want.

Instead, you need to use properly:

use XYZ qw($VAR);

However, XYZ doesn't have an import to run here, so you need to update that. There are two ways to fix XYZ to do this - one is to import import, e.g., use Exporter qw(import);, the other is to derive off Exporter, e.g., use parent qw(Exporter);. Both of these will get XYZ->import(...) to work properly.

Once XYZ is useing Exporter correctly, then the use XYZ qw($VAR); line will cause perl to implicitly load XYZ and call XYZ->import(qw($VAR)), which will import that variable into your namespace.

Now, having answered your question, I will join others in suggesting that exporting variables is a very bad code smell, and probably is not the best / cleanest way to do what you want.

Tanktalus
  • 21,664
  • 5
  • 41
  • 68