Professional Perl | 12
[previous] [next] |
Professional Perl
Passing Lists and Hashes
We mentioned earlier, when we started on the subject of passed arguments, that passing lists and hashes directly into a subroutine causes list flattening to occur, just as it does with ordinary list definitions. Consequently, if we want to pass an array or hash to a subroutine, and keep it intact and separate from the other arguments, we need to take additional steps. Consider the following snippet of code:
$message = "Testing";
@count = (1, 2, 3);
testing ($message, @count); # calls 'testing' -- see below
The array @count is flattened with $message in the @_ array created as a result of this subroutine, so as far as the subroutine is concerned the following call is actually identical:
testing ("Testing", 1, 2, 3);
In many cases this is exactly what we need. To read the subroutine parameters we can just extract the first scalar variable as the message and put everything else into the count:
sub testing {
($message, @count) = @_;
...
}
Or, using shift:
sub testing {
$message = shift;
# now we can use @_ directly in place of @count
...
}
The same principle works for hashes, which as far as the subroutine is concerned is just more values. It is up to the subroutine to pick up the contents of @_ and convert them back into a hash:
sub testing {
($message, %count) = @_;
print "@_";
}
testing ("Magpies", 1 => "for sorrow", 2 => "for joy", 3 => "for health", 4 => "for wealth", 5 => "for sickness", 6 => "for death");
However, this only works because the last parameter we extract inside the subroutine absorbs all the remaining passed parameters. If we were to write the subroutine to pass the list first and then the scalar afterwards, all the parameters are absorbed into the list and the scalar is left undefined:
sub testing {
(@count, $message) = @_; # ERROR
print "@_";
}
testing(1, 2, 3, "Testing");
# results in @count = (1, 2, 3, "Testing") and $message = undef
If we can define all our subroutines like this we won't have anything to worry about, but if we want to pass more than one list we still have a problem. If we attempt to pass both lists as-is, then extract them inside the subroutine, we end up with both lists into the first and the second left undefined:
sub testing {
my (@messages, @count) = @_; # wrong!
print "@_";
}
@msgs = ("Testing", "Testing");
@count = (1, 2, 3);
testing(@msgs, @count);
# results in @messages = ("Testing", "Testing", "Testing", 1, 2, 3) and
# @count = ();
The correct way to pass lists and hashes, and keep them intact and separate, is to pass references. Since a reference is a scalar, it is not flattened like the original value and so our data remains intact in the form that we originally supplied it:
testing (["Testing", "Testing"], [1, 2, 3]); # with two lists
testing (\@messages, \@count); # with two array variables
testing ($aref1, $aref2); # with two list references
Inside the subroutine we then extract the two list references into scalar variables and dereference them using either @{$aref} or $aref->[index] to access the list values:
sub testing {
($messages, $count) = @_;
# print the testing messages
foreach (@ {$messages}) {
print "$_ ... ";
}
print "\n";
# print the count;
foreach (@ {$count}) {
print "$_! \n";
}
}
Another benefit of this technique is efficiency; it is better to pass two scalar variables (the references) than it is to pass the original lists. The lists may contain values that are large both in size and number. Since Perl must store a local copy of the @_ array for every new subroutine call in the calling stack, passing references instead of large lists can save Perl a lot of time and memory.
[previous] [next] |
Revised: March 22, 2001
Created: March 22, 2001