http://qs1969.pair.com?node_id=390592

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

Hi monks,

I would like to seek your views on the merits/demerits of

(1) declaring a shared variable as global versus

(2) passing a localised variable from one subroutine to the next one that requires its value.

With (1), the variable is declared at the top level. Somewhere in the code, it gets assigned a value and that value is used by other subroutines.

With option (2), a localised variable is declared from within a subroutine. This variable is passed from this subroutine to the next one and to the ones after that which use that value.

I look forward to reading your views :)

Thanks in anticipation.

  • Comment on Global variable vs passing variable from sub to sub

Replies are listed 'Best First'.
Re: Global variable vs passing variable from sub to sub
by Ovid (Cardinal) on Sep 13, 2004 at 16:32 UTC

    Consider the following snippet:

    sub account_over_limit { my ($account, $balance) = @_; return exists $LIMITED_ACCOUNTS{$account} && $balance > $LIMIT; }

    You might think that since the %LIMITED_ACCOUNT and $LIMIT variables are relatively static, declaring them as globals is a good idea. In reality, this makes life very difficult if you need to refactor the code or track down bugs. Obviously, you now have a routine that cannot be cut and pasted into another package as part of a refactoring strategy because you're dependent on external data. This is a big problem.

    Next, consider what happens when you realize that different accounts have different limits. Your code changes $LIMIT at one point and forgets to reset it. Now your &account_over_limit subroutine returns spurious results, but this problem might not show up for weeks until your accounting department realizes that some people have received more credit than they should, or are complaining when they're not allowed to buy something.

    Cheers,
    Ovid

    New address of my CGI Course.

      Thanks for your very specific example, Ovid!

      I do have things like that in my code but they are kept to the minimal.

•Re: Global variable vs passing variable from sub to sub
by merlyn (Sage) on Sep 13, 2004 at 16:24 UTC
    Both of them are bad. Both "passing around" and "global variables" are a signs of a misdesign.

    If you have a value that must be shared, make it a "regional" variable, accessible to a few subroutines that deal with it, and in turn provide a higher-level interface that don't need to expose that variable. For example, if you're talking about something like a database handle, create a module that does all the DB stuff, and then you can initialize (perhaps lazily) the database handle there. Thus, the rest of the program does not have access to the database handle, but you still don't have to pass it around a lot.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      Thanks, merlyn!

      Hm...I'm not quite sure if what I'm currently labelling as "global" is in fact "regional" in my implemenation. The "global" I mentioned in the parent node is used in a module and is shared by a couple of subroutines. If I understand you correctly, that's okay right?

Re: Global variable vs passing variable from sub to sub
by dragonchild (Archbishop) on Sep 13, 2004 at 16:15 UTC
    I seriously hope I don't have to describe to you the plethora of perils encountered when using global variables. Bugs are notoriously difficult to track down and the code generally devolves into a mess.

    That said, selective usage of global variables can often be a big benefit to a program's maintainability. For example, most session implementations in web applications are glorified global variables. Using a sessionstore can significantly reduce the amount of tramp data that goes on the URL.

    Often, specific variables are maintained globally, such as $dbh. With proper documentation, I don't see a problem with this. I would (and do) prefer using a singleton instead of an explicit global, just so that new usages can be easily added. But, styles differ.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

    I shouldn't have to say this, but any code, unless otherwise stated, is untested

      Thanks, dragonchild!

      I'm aware of the mess having lots of globals can create so I generally try to keep them to the minimal.

Re: Global variable vs passing variable from sub to sub
by grinder (Bishop) on Sep 13, 2004 at 17:05 UTC

    The main trouble is that the content of the variable is unprotected. Nothing is stopping any code anywhere from setting it to whatever it likes. It may not be malicious: it just might be that someone has a variable with a proximate name, and by a slip of the keyboard they type the name of the global variable inadvertently).

    If unexpected changes are occurring to it, you will want to find where it is being changed. You will have little choice but to step through the code line by line in the debugger to find the offending code. This can be tedious.

    When you sit down and think about a global variable, in nearly all cases, all you want to do is to set it once, and from then on, refer to it ever after. In that case, the following snippet will get you on your way:

    BEGIN { my $var; sub set_var { $var = shift; } sub var { $var; } }

    If you want to get paranoid, it would easy to extend the above to allow you to set the value one time only, (e.g., test if the contents are undefined) and any subsequent calls to set_var are silently ignored.

    Another frequent thing to do with a global variable is to be able to increment a variable (but not be able to set it to an arbitrary value), and to read the current value back:

    BEGIN { my $var = 0; sub bump_var { ++$var; } sub var { $var; } }

    In both cases, the $var variable is protected: you can't get at the variable directly to fiddle with it. You can't even define a new subroutine to get at it. All you have are the officially sanctioned routines to manipulate it.

    And the really great thing with this approach is that if you have some errant code that is setting $var to some wacky value, or incrementing it when you don't expect it, all you have to do is to hang a breakpoint off the name of the sub in the debugger, and then just let it run at full clip until the routine gets called. Much less tedious.

    Oh, and I wouldn't call these routines var, I'd use a more descriptive name.

    - another intruder with the mooring of the heat of the Perl

      If unexpected changes are occurring to it, you will want to find where it is being changed. You will have little choice but to step through the code line by line in the debugger to find the offending code. This can be tedious.

      ...

      And the really great thing with this approach is that if you have some errant code that is setting $var to some wacky value, or incrementing it when you don't expect it, all you have to do is to hang a breakpoint off the name of the sub in the debugger, and then just let it run at full clip until the routine gets called. Much less tedious.

      By tie()ing the global variable you can add the same functionality and get the same benefit that of a subroutine wrapper in your examples. Then you even get compile-time spelling checks. ;-)

      ihb

      Read argumentation in its context!

Re: Global variable vs passing variable from sub to sub
by edan (Curate) on Sep 13, 2004 at 17:46 UTC

    I'm surprised nobody has mentioned it yet, but this is one of the aspects or programming that Objects have come to solve. Namely, this is solved by encapsulation. That's when you hide your data (those pesky global variables) inside the object, and then you attach methods to the object to handle the data, and it all happens in a nice, self-contained (encapsulated, you might say) way.

    It's not hard to write your own little module implementing a class which wraps up the global variables and their associated methods. See perltoot for more info.

    --
    edan

Re: Global variable vs passing variable from sub to sub
by pelagic (Priest) on Sep 13, 2004 at 19:37 UTC
    A object oriented solution is IMHO the best solution for global variables. If you want to dive into this topic I suggest to read brian d foys review of singleton design patterns.

    pelagic
Re: Global variable vs passing variable from sub to sub
by gmpassos (Priest) on Sep 14, 2004 at 05:24 UTC
    Humm, don't forget that Perl uses a special variable @_ for the arguments. What you can't forget is that this array @_ is global, but it's elements will change for each call. So, the performance of @_ will be similar to a global variable, but will work as a stack, so, will work for multiple calls of the same sub and recursivelly.

    This code will show that:

    sub test1 { ++$_[0] ; print "T1 [@_]\n" ; test2($_[0]) ; } sub test2 { ++$_[0] ; print "T2 [@_]\n" ; test3($_[0]) ; } sub test3 { ++$_[0] ; print "T3 [@_]\n" ; print "end\n" ; } my $n = 10 ; test1($n) ; print "N: $n\n" ;
    output:
    T1 [11] T2 [12] T3 [13] end N: 13
    And forget the use of a global variable for any type of code and start learning OO. Take a look at perlobj, perltoot, perlboot and perltooc.

    For Object Orientation (OO) I use Class::HPLOO.

    Graciliano M. P.
    "Creativity is the expression of the liberty".

      Thanks for your input, gmpassos!

      I would love to switch to the OO mode some day. I've read quite a bit on it and have even got some OO code to work (got some help from castaway). Still, I'm not quite ready yet. Maybe it's just that I think better in terms of procedures (verbs rather than nouns).

      I suppose if I were to write a specific standalone module that serves a single purpose, I might be able to think the OO way much better.

Re: Global variable vs passing variable from sub to sub
by sleepingsquirrel (Chaplain) on Sep 14, 2004 at 18:22 UTC
    If you're really hardcore, you might find inspiration from this paper.


    -- All code is 100% tested and functional unless otherwise noted.
Re: Global variable vs passing variable from sub to sub
by Wassercrats (Initiate) on Sep 14, 2004 at 07:39 UTC
    Yeah, that's right. I'm back, and I have something to say. This is really a reply to people who've given code examples in this thread.

    I don't understand the last line of:

    sub account_over_limit { my ($account, $balance) = @_; return exists $LIMITED_ACCOUNTS{$account} && $balance > $LIMIT; }
    And I don't understand the point of:

    BEGIN { my $var; sub set_var { $var = shift; } sub var { $var; } }

    I've had numerous problems trying to determine where a global was changed, and I have a feeling there's a good anti-global argument around here somewhere, but I can't find it. When a script is doing alot of calculating and outputting just the end result, I don't think it matters how localized things are. You still won't know what affected the output.

    Another, barely related thing--I'm just about done with a GNU tar GUI for Windows, and I'm finding good uses for goto all over the place.

    Anyway, please make your code examples as simple as possible and explain them properly.

      I don't understand the last line of:
      sub account_over_limit { my ($account, $balance) = @_; return exists $LIMITED_ACCOUNTS{$a­ccount} && $balance > $LIMIT; }
      This is incredibly simple perl code. It simple accesses a hash and compares a scalar to another scalar. If you don't understand this perhaps you should read some tutorials?
      And I don't understand the point of:
      BEGIN { my $var; sub set_var { $var = shift; } sub var { $var; } }
      sub; is a compile time statement, so the BEGIN block forces the assignment to happen at compile time also. Also it helps lexically scope $var so nothing else can touch it. Similar to inside out objects.
      've had numerous problems trying to determine where a global was changed, and I have a feeling there's a good anti-global argument around here somewhere, but I can't find it. When a script is doing alot of calculating and outputting just the end result, I don't think it matters how localized things are. You still won't know what affected the output.
      Let me put it this way and see if it makes more sense. I have a 11,000 line program. It has 10 globals at the top. Because they're global, this means that *any* single one of those 11,000 lines can modify that global value. Therefor, when your global has the *wrong* value, you have problems finding it, because you must examine 11,000 lines of code.

      Using properly scoped lexicals, you might have a function that is only 10 lines long with a lexical declared at the top. Now you know only 10 lines total can affect that variable, thus vastly reducing the search space.

      Now, you might say, "What if I pass that variable to another function?". In that case, the value in the function you pass from won't be changed, so you don't have to worry about it. The function you pass it to might later change the value, but again you only have that function to examine.

      You might also say, "What if I assign the result of a function to my lexical?". In this case, you again have a vastly limited scope to search as you only need to examine any code in that function.

      The whole point of lexicals is to reduce the complexity of the code.
        Hi BUU,

        Let me put it this way and see if it makes more sense. I have a 11,000 line program. It has 10 globals at the top. Because they're global, this means that *any* single one of those 11,000 lines can modify that global value. Therefor, when your global has the *wrong* value, you have problems finding it, because you must examine 11,000 lines of code. Using properly scoped lexicals, you might have a function that is only 10 lines long with a lexical declared at the top. Now you know only 10 lines total can affect that variable, thus vastly reducing the search space.
        I think this is an extreme case where globals are used all over the place. Maybe they are cases like that.

        I once had the unfortunate task of tweaking someone's script where not only were the variables global, they were also not declared with my. It was unpleasant, to say the least.

        However, I think there's some justification for selective use of what meryln termed "regional" globals - globals used within a module (if I understood him correctly).

      A reply falls below the community's threshold of quality. You may see it by logging in.
      This has already been covered in other posts, but this may clarify things for Wassercrats.

      If you don't scope your variables, it is not immediately obvious whether:
      1) You variables will have changed by calling a sub.
      2) If the variables did change, whether they were changed on purpose, or accidently.

      $x = 0; DoStuffX(); print "As long as no one used global variables, I know that x = 0 ", "without needing to know what DoStuffX does.\n"; if ($x != 0) { print "Nope, I need to spend time looking through ", "DoStuffX to find out why x changed. I really ", "wish the guy used \$x=DoStuffX(\$x) so that I ", "would know whether he changed x intensionally ", "or not.\n\n" }; $y = 0; DoStuffY($y); print "As long as no one used global variables, I know that y = 0 ", "without needing to know what DoStuffY does.\n"; if ($y == 0) { print "Yippie! I can go home early!\n\n" }; sub DoStuffX { # lost of code involving $x $x=$x+1; # lots of code involving $x }; sub DoStuffY { my $y = shift @_; # lost of code involving $y $y=$y+1; # lots of code involving $y };