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

If a subroutine exists in my main script, I don't have to pass a variable into it and get a modified value out of it. But, if I put the subroutine in "common.pl" (and "require" it) then it doesn't have access to the variables of the main script it is required into.

Is there some way to make required libraries see the variables of the script that required them? Is there some way to pass variables to a subroutine so that it modifies the original value?

Right now I have to ($x, $y, $z) = subroutine($x, $y, $z). In one sense this seems like good documentation. But, in another sense the subroutine is making its own local copy of the variables when it shouldn't have to.

Is this where I should be looking at objects? Should my required library really be a package that will share in any "our" definitions?

I've written Perl for 5 years and never understood how to do common subroutines without passing obvious global values in and out of them.

Thanks!

Replies are listed 'Best First'.
Re: Global vars?
by BrowserUk (Patriarch) on Oct 29, 2003 at 04:16 UTC

    Issues of whether using global vars in subroutines is a good idea aside, you could look at do EXPR;. As in

    do 'common.pl';

    If the file common.pl contains just subroutines, these will be parsed and become a part of the calling script and will have the same scope as if they were copied into the script at the same place. Do EXPR; also uses %INC to find the file just like require.

    P:\test>echo sub t{ print $global; } >test.pl P:\test>perl -e" do 'test.pl'; $global = 'Hello world!'; t();" Hello world!

    Kind of the perl equivalent of C's #include, with all the caveats that entails. I'm not necessarily recommending it, but if your going to go that way anyway, perl will let you.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Hooray!

Re: Global vars?
by Roger (Parson) on Oct 29, 2003 at 04:17 UTC
    No need for global variables. You surely can do this with pass by reference -
    use strict; my $X = 1; my $Y = 2; my $Z = 3; print "before: X=$X, Y=$Y, Z=$Z\n"; pass_by_ref(\$X, \$Y, \$Z); print "after: X=$X, Y=$Y, Z=$Z\n"; sub pass_by_ref { my ($x, $y, $z) = @_; ${$x} = 2; # or $$x = 2; ${$y} = 3; ${$z} = 4; }
    And the output is -
    before: X=1, Y=2, Z=3 after: X=2, Y=3, Z=4
Re: Global vars?
by Zaxo (Archbishop) on Oct 29, 2003 at 04:39 UTC

    This is the sort of thing that prototypes are good for. You get to pass variables seemingly by value, but it is really a reference that is passed. That allows the original variable to be modified.

    sub mogrify (\$\$\$) { my ($xref, $yref, $zref) = @_; # do stuff to $$xref and friends 1; } mogrify $x, $y, $z;
    That will mogrify the variables in place.

    After Compline,
    Zaxo

      Given the existence of virtually any alternative, I would recommend against prototypes.

      What if the person uses & in the function call? What if the person you are giving advice to uses it everywhere and then tries to learn how OO works? What if the person tries to put the variables into an array and then calls your function?

      Prototypes add a lot of gotchas and not a lot of benefits. Therefore I avoid recommending them even when they look like they might make sense. For instance the following is no more obscure, and has fewer potential issues:

      sub mogrify { my ($xref, $yref, $zref) = \@_[0..2]; # do stuff to $$xref and friends 1; }
Re: Global vars?
by TomDLux (Vicar) on Oct 29, 2003 at 04:18 UTC

    To begin with, accessing global variables from a subroutine is poor programming practice.

    If you have a large number of separate variables, I bet some of them belong together, just like the left and right headlights and left and right windshield wipers and the four tires on my car belong together. You could combine these into a more complex data structure, a car, and then pass only one variable, instead of eight.

    Instead of passing the variable, and having the data copied inside the routine, and then copied again on the return from the routine, pass a referencece to the variable, so that no copying is done at all.

    if you've now grouped variables and routines into various categories, say those dealing with your car, those dealing with your kitchen, those dealing with your printer, aggregate the data and routines into objects. While it may seem excessive for a small project, the objects will probably be small and simple, and hopefully you can re-use them in other projects. Type perldoc perlboot ( then perldoc perltoot1, and next perldoc perltoot2 ) to learn about objects, and then buy Randall Schwatrz's Learning Perl Objects, References & Modules to perfect your understanding.

    --
    TTTATCGGTCGTTATATAGATGTTTGCA

Re: Global vars?
by meetraz (Hermit) on Oct 29, 2003 at 16:17 UTC
    Just to propose a TMTOWTDI solution that nobody else suggested yet..

    Variables passed to a subroutine are automatically aliased to the @_ array, so if you modify them, you modify the original variables passed in:

    use strict; my $X = 1; my $Y = 2; my $Z = 3; print "before: X=$X, Y=$Y, Z=$Z\n"; modifyvars($X, $Y, $Z); print "after: X=$X, Y=$Y, Z=$Z\n"; sub modifyvars { $_[0] = 2; $_[1] = 3; $_[2] = 4; }
    Though this isn't too easy to read. I would have to agree with Zaxo -- if your version of Perl is new enough, prototypes make this cleaner to look at.
Re: Global vars?
by hmerrill (Friar) on Oct 29, 2003 at 13:50 UTC
      Right now I have to ($x, $y, $z) = subroutine($x, $y, $z). In one sense this seems like good documentation. But, in another sense the subroutine is making its own local copy of the variables when it shouldn't have to.

    Why do you think a subroutine making local copies of parameters coming in is a bad thing? You could do as Roger suggested and pass the variables by reference, but then the code to set those "reference" variables becomes somewhat harder to understand - especially for newbies.

    If your concern is taking up too much memory for creating unnecessary variables, then - I could be wrong about this, but here is my thought - creating local variables in a subroutine will take more memory, but as soon as those variables go out of scope (at the end of the subroutine), that memory is then made available again. So I think(?) that only causes a slight increase in memory usage, and I believe it's only for the scope of the subroutine.

    My preference is to keep the code as simple and understandable as possible - for everyone, including newbies. And, as you already said, it makes the code more self documenting - I prefer to do just as you did:

    sub do_something { my $a = shift; # assign 1st arg to $a my $b = shift; # assign 2nd arg to $b my $c = shift; # assign 3rd arg to $c ### now do something with $a, $b, $c return ($a, $b, $c); } my $x = 1; my $y = 2; my $z = 3; my ($x, $y, $z) = do_something($x, $y, $z); Having a subroutine 1. take parameters in, and assign to local variables 2. process the local variables 3. return what needs to be returned

    keeps the code modular, and fully encapsulates the functionality within the subroutine, so that all the caller needs to do is know the api for what to give it, and what it will give back. And the calling code is kept clean and simple and self-documenting as well - you know not only what you're giving it, but also what you're getting back.

    If you instead use the pass-by-reference approach, then in order to understand the calling code, you also need to understand the subroutine code since the calling code is no longer self explanatory. IMHO, pass-by-reference is fine, but makes the code more complex (not as easy to understand).