Passing Lists and Hashes - Page 10
July 13, 2001
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 are 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 in 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.
Passing Parameters - Page 9
Professional Perl Programming
Converting Scalar Subroutines into List Processors - Page 11
|