Professional Perl | 19
[previous] [next] |
Professional Perl
Determining and Responding to the Calling Context
Sometimes it is useful to know what the calling context is, so we can return different values based on the caller's requirements. The return statement already knows this implicitly, and makes use of the context to save time, returning a count of a returned list if the subroutine is called in a scalar context. This is more efficient than returning all the values in the list and then counting them - passing back one scalar is simpler when that is all the calling context actually requires.
Perl allows subroutines to directly access this information with the wantarray function. Using wantarray allows us to intelligently return different values based on what the caller wants. For example, we can return a list either as a reference or a list of values, depending on the way in which we were called:
return wantarray? @files: \@files;
We can also use wantarray to return undef or an empty list depending on context, avoiding the problems of assigning undef to an array variable as we discussed above:
return wantarray? (): undef;
Modifying our original subroutine to incorporate both these changes gives us the following improved version of list_files that handles both scalar and list context:
sub list_files {
my $path = shift;
return wantarray? ():undef unless defined $path;
my @files = glob "$path/ *";
return wantarray? @files: \@files;
}
This is an example of Perl's reference counting mechanism in action, @files may go out of scope, but the reference returned in scalar context preserves the values it holds.
We can now call list_files with two different results. In list context we get either a list of files, or an empty list if either no files were found, or the path is undefined. This allows the return value to be used in a foreach loop. If we want to distinguish between a defined path with no files and an undefined path we call list_files in scalar context. In return, we get a reference to a list of files, a reference to an empty list if no files were found, or the undefined value if the path was undefined. By additionally testing for the undefined value with defined, we can now distinguish all three cases:
# list context
@files = list_files ($ARGV[0]);
die "No path defined or no files found" unless @files;
print "Found: @files \n";
# scalar context
$files = list_files($ARGV[0]);
die "No path defined! \n" unless defined $files;
die "No files found! \n" unless $files;
print "Found: @{$files} \n";
One final note about wantarray: If we want to find the number of files rather than retrieve a list, then we can no longer call the subroutine in scalar context to achieve it. Instead, we need to call the subroutine in list context and then convert it into a scalar explicitly:
$count = $#{list_files $ARGV[0]}+1;
This is much clearer, because it states that we really do mean to use the result in a scalar context. Otherwise, it could easily be a bug that we have overlooked. However, be very careful not to use scalar here. We often use scalar to count arrays, but scalar forces its argument into a scalar context. $# requires that its argument is a list, and then counts it.
Handling Void Context
So far we have considered list and scalar contexts. If the subroutine is called in a void context undefined. We can use this fact to save time computing a return value, or even to produce an error:
sub list_files {
die "Function called in void context" unless defined wantarray;
...
}
Handling Context: an Example3
Putting all the above together, here is a final version of list_files that handles both scalar, list, and void contexts, along with a sample program to test it out in each of the three contexts:
#!/usr/bin/perl
#listfile.pl
use warnings;
use strict;
sub list_files {
die "Function called in void context" unless defined wantarray;
my $path = shift;
return wantarray?():undef unless defined $path;
chomp $path; #remove trailing linefeed, if present
$path.='/*' unless $path =~/\*/; #add wildcard if missing
my @files = glob $path;
return wantarray?@files:\@files;
}
print "Enter Path: ";
my $path = <>;
# call subroutine in list context
print "Get files as list:\n";
my @files = list_files($path);
foreach (sort @files) {
print "\t$_\n";
}
# call subroutine in scalar context
print "Get files as scalar:\n";
my $files = list_files($path);
foreach (sort @{$files}) {
print "\t$_ \n";
}
# to get a count we must now do so explicitly with $#...
# note that 'scalar would not work, it forces scalar context.
my $count = $#{list_files($path)}+1;
print "Count: $count files\n";
# call subroutine void context - generates an error
list_files($path);
The name wantarray is something of a misnomer, since there is no such thing as 'array context'. A better name for it would have been wantlist.
[previous] [next] |
Created: April 18, 2001
Revised: April 18, 2001