14 Part 12: Side Effects with Perl Variables

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.