In the following script, I declare and modify @basearray
in the main program. Inside the dosomething
subroutine, I access @basearray
, assign it to an array local to the script, and modify the local copy. Because I have been careful to change the value only of local variables inside the subroutine, @basearray
is not changed.
If I had made the mistake of assigning a value to @basearray
inside the subroutine, though, it would have been changed and that value would have persisted after the call to the subroutine.
This is demonstrated in the 2nd subroutine, doagain
.
Also, doagain
receives the reference \@basearray
as an argument rather than accessing @basearray
directly. But going to that additional trouble provides no additional safety. Why do it that way at all?
Is there a way to guarantee that I cannot inadvertently change @basearray
inside any subroutine? Any kind of hard safety device that I can build into my code, analogous to use strict;
, some combination perhaps of my
and local
?
Am I correct in thinking that the answer is No, and that the only solution is to not make careless programmer errors?
#!/usr/bin/perl
use strict; use warnings;
my @basearray = qw / amoeba /;
my $count;
{
print "\@basearray==\n";
$count = 0;
foreach my $el (@basearray) { $count++; print "$count:\t$el\n" };
}
sub dosomething
{
my $sb_name = (caller(0))[3];
print "entered $sb_name\n";
my @sb_array=( @basearray , 'dog' );
{
print "\@sb_array==\n";
$count = 0;
foreach my $el (@sb_array) { $count++; print "$count:\t$el\n" };
}
print "return from $sb_name\n";
}
dosomething();
@basearray = ( @basearray, 'rats' );
{
print "\@basearray==\n";
$count = 0;
foreach my $el (@basearray) { $count++; print "$count:\t$el\n" };
}
sub doagain
{
my $sb_name = (caller(0))[3];
print "entered $sb_name\n";
my $sf_array=$_[0];
my @sb_array=@$sf_array;
@sb_array=( @sb_array, "piglets ... influenza" );
{
print "\@sb_array==\n";
$count = 0;
foreach my $el (@sb_array) { $count++; print "$count:\t$el\n" };
}
print "now we demonstrate that passing an array as an argument to a subroutine does not protect it from being globally changed by programmer error\n";
@basearray = ( @sb_array );
print "return from $sb_name\n";
}
doagain( \@basearray );
{
print "\@basearray==\n";
$count = 0;
foreach my $el (@basearray) { $count++; print "$count:\t$el\n" };
}
(Putting my comment as answer) One way to guarantee not changing a variable inside a subroutine is to not change it. Use only lexically scoped variables inside the subroutine, and pass whatever values you need inside the subroutine as arguments to the subroutine. It is a common enough coding practice, encapsulation.
One idea that you can use -- mainly as practice, I would say -- to force yourself to use encapsulation, is to put a block around your "main" code, and place subroutines outside of it. That way, if you should accidentally refer to a (formerly) global variable, use strict
will be able to do it's job and produce a fatal error. Before runtime.
use strict;
use warnings;
main: { # lexical scope reduced to this block
my @basearray = qw / amoeba /;
print foo(@basearray); # works
print bar(); # fatal error
} # END OF MAIN lexical scope of @basearray ends here
sub foo {
my @basearray = @_; # encapsulated
return $basearray[1]++;
}
sub bar {
return $basearray[1]++; # out of scope ERROR
}
This will not compile, and will produce the error:
Global symbol "@basearray" requires explicit package name at foo.pl line 15.
Execution of foo.pl aborted due to compilation errors.
I would consider this a training device to force yourself to using good coding practices, and not something to necessarily use in production code.