[Courses] [Perl] Part 12: Side Effects with Perl Variables

Dan Richter daniel.richter at wimba.com
Thu Oct 9 13:38:06 EST 2003


LinuxChix Perl Course Part 12: Side Effects with Perl Variables

1) Introduction
2) Variable Scope
3) Side Effects
4) [Non-]Exercise
5) Answer to Previous Exercise
6) Past Information
7) Credits
8) Licensing

             -----------------------------------

1) Introduction

Last week we saw how to use Perl's special variables. This week we are 
going to see an important caveat concerning these special variables. 
Developing good habits now will save us lots of debugging when we start 
writing larger programs.

             -----------------------------------

2) Variable Scope

Like most languages, Perl includes the concept of "scope". The scope of 
a variable is the part of the code in which it can be accessed.

   if ( $num != 45 ) {
     my $foo = 'bar';
     print "$foo\n";    # OK
   }
   print "$foo\n";      # D'oh! Went out of scope.

Of course, Perl will only warn us of this mistake if we "use strict". 
(Otherwise, it will just create a new variable named "$foo".)

In Perl, the scope of a variable is determined (partly) by the depth of 
the "code block" in which it was declared. A "code block" is delimited 
by opening and closing braces. Note that you can create a code block 
without using keywords such as "if":

   my $foo = 'abc';
   {       # Start inner code block.
     print "Before redefinition, foo = $foo\n";
     my $foo = 'xyz';   # New declaration in this code block.
     print "Inside code block, foo = $foo\n";
   }
   print "Outside code block, foo = $foo\n";

Try the above code. As you can see, the inner code block "inherits" the 
variables defined outside it. By contrast, variables declared inside the 
code block "vanish" when the code block is finished, even if they have 
the same names as variables declared outside the code block.

             -----------------------------------

3) Side Effects

All Perl variables are global unless explicitly declared otherwise 
(e.g., with "my"). If a global variable "$foo" appears in several places 
in your program, it's the same "$foo", even if it's mentioned in a 
separate library. This can wreak all kinds of havoc because you might 
have used the name "$foo" without knowing that there was a "$foo" in 
some library that you happened to use. This is called a "side effect", 
and it's usually not wanted. This is why you should always declare your 
variables with "my", which makes them private rather than global.

What about the special variables we talked about last week? As we have 
seen, they change the workings of Perl functions and operators. (For 
example, "$/" changes the way "<STDIN>" breaks "lines".) This is an 
expected and desired side effect.

But when we start using functions (other than those built into the 
language), we may run into situations where one part of the code doesn't 
anticipate the fact that another part of the code changed a global 
variable. Here is an example:

   # Declare function "home_dir". Takes a
   # username and returns its home directory.
   sub home_dir {
     open PASSWD, '< /etc/passwd' or die "Uh oh: $!";
     $/ = ':';               # Records separated by colons.
     # ...
     close PASSWD;
     return $home;
   }

   # Main body.
   # Read usernames (one per line).
   open FILE, '< somefile.txt' or die "NO!!!";
   while ( my $username = <FILE> ) {
     chomp($username);
     my $home = home_dir($username);
     print "$username: $home\n";
   }
   close FILE;

Because "$/" is global, the "$/" in the function is the same as the "$/" 
in the main body. So setting "$/" in the function changes the way 
"<FILE>" operates in the main body. This type of bug is difficult to 
track down.

We cannot solve the problem by declairing "$/" with "my" because we want 
it to have some side effects (besides, Perl will yell at you if you 
try). So we declare it as "local". Variables declared with "local" can 
be accessed by functions called within the code block, but after the 
code block is closed (with a "}") the change ceases to take effect. You 
should usually use "local" when you change a special Perl variable:

   # Declare function "home_dir".
   sub home_dir {
     open PASSWD, '< /etc/passwd' or die "Uh oh: $!";
     local $/ = ':';       # Declare variable and set value.
     # ...
     close PASSWD;
   }

But there's an even more subtle trap here. If we had changed the 
variable in the main body and not the function, the change would have 
been reflected in the function even if it was declared as local in the 
main body:

   # Stupid function; just for demonstration.
   sub read_line {
     return <STDIN>;
   }

   # Main body.
   local $/ = ',';
   # ...
   print read_line() . "\n";

In this example, setting "$/" has an effect on "read_line" even though 
"$/" was declared as "local". It's important that you understand the 
difference between this example and the previous one. One way to guard 
against this later problem is to enclose the changed variable in a code 
block:

   # Main body.
   {   # Introduce code block; scope is reduced to this block.
     local $/ = ',';
     # ...
   }   # End of block. Local $/ goes out of scope.
   print read_line() . "\n";

But this implies knowing that "read_line" would be affected by your 
changed variable. Are you supposed to check the implementation of every 
function you call to see if it uses any built-in Perl variables? Of 
course not! Instead, the rule is: as much as possible, you should not 
call ANY function when you have changed a special variable, unless you 
intended the change to affect that function. The correlary to that rule 
is: make the code block containing the changed variable as small as 
possible.

It's especially important to declare "$_" as local in functions, to 
avoid this problem:

   # Change employee list to keep up with people quitting.
   while ( <IN_FILE> ) {
     my $quit_person = GetPersonWhoQuit();   # Did this change "$_"?
     s/$quit_person//g;        # Uh oh: where did the line of input go?
     print OUT_FILE;           # I wonder what we just output!
   }

Of course, you should use your common sense here. For example, when you 
change "$<" (user ID), you probably want the change to be permanent and 
global. Likewise, functions should definitely declare "$_" as local, but 
declaring "$!" (last error) as local is probably less important because 
that variable is expected to change - indeed, the caller might use "$!" 
to understand why the function failed. Think carefully about the 
expected behavior, but when in doubt, avoid side effects.

You have been warned!

             -----------------------------------

4) [Non-]Exercise

There's no exercise this week.

             -----------------------------------

5) Answer to Previous Exercise

Here is a previous program which does not explicitly name any variable.

   #!/usr/bin/perl -w
   use strict;

   while ( <STDIN> ) {
     s/\bdead\b/metabolically different/g;
     print;
   }

             -----------------------------------

6) Past Information

Part 1:  Getting Started
      http://linuxchix.org/pipermail/courses/2003-March/001147.html

Part 2:  Scalar Data
      http://linuxchix.org/pipermail/courses/2003-March/001153.html

Part 3:  User Input
      http://linuxchix.org/pipermail/courses/2003-April/001170.html

Part 4:  Control Structures
      http://linuxchix.org/pipermail/courses/2003-April/001184.html

Part 4.5, a review with a little new information at the end:
      http://linuxchix.org/pipermail/courses/2003-July/001297.html

Part 5:  The "tr///" Operator
      http://linuxchix.org/pipermail/courses/2003-July/001302.html

Part 6:  The "m//" Operator
      http://linuxchix.org/pipermail/courses/2003-August/001305.html

Part 7:  More About "m//"
      http://linuxchix.org/pipermail/courses/2003-August/001322.html

Part 8:  The "s///" Operator
      http://linuxchix.org/pipermail/courses/2003-August/001330.html

Part 9:  Simple File Access
      http://linuxchix.org/pipermail/courses/2003-September/001340.html

Part 10: Executing Commands with "open"
      http://linuxchix.org/pipermail/courses/2003-September/001344.html

Part 11: Perl Variables
      http://linuxchix.org/pipermail/courses/2003-October/001345.html

             -----------------------------------

7) Credits

Works cited:
a) "man perlvar"
b) Kirrily Robert, Paul Fenwick and Jacinta Richardson's
    "Intermediate Perl", which you can find (along with their
    "Introduction to Perl") at:
    http://www.perltraining.com.au/notes.html

Thanks to Jacinta Richardson for fact checking.

             -----------------------------------

8) Licensing

This course (i.e., all parts of it) is copyright 2003 by Alice Wood and 
Dan Richter, and is released under the same license as Perl itself 
(Artistic License or GPL, your choice). This is the license of choice to 
make it easy for other people to integrate your Perl code/documentation 
into their own projects. It is not generally used in projects unrelated 
to Perl.




More information about the Courses mailing list