Returning the Undefined Value - Page 20
July 27, 2001
Although it might seem a strange idea, it is quite common for
subroutines and many of Perl's built-in functions to return the
undefined value undef instead of a real (that is,
defined) value.
The advantage of undef is that it evaluates to
'False' in conditions, but is distinct from a simple zero because
it returns False when given as an argument to
defined. This makes it ideal for use in subroutines
that want to distinguish a failed call from one that just happens
to return no results. This modified version of
list_files uses undef to flag the
caller when no path is specified:
#!/usr/bin/perl
# findfiles.pl
use warnings;
use strict;
my $files = list_files ($ARGV[0]);
if (defined $files) {
if ($files) {
print "Found: $files \n";
} else {
print "No files found \n";
}
} else {
print "No path specified\n";
}
sub list_files {
my $path = shift;
return undef unless defined $path;
# return an empty list if no path
return join(',', glob "$path/*");
# return comma separated string
}
If no path is supplied, the subroutine returns
undef, which evaluates to False in the
if statement. If the path was supplied but no files
were found, the subroutine returns an empty string which would
evaluate to False on its own but is still defined and so tests
True in the if statement. We then test the value of
$files with the ternary operator and print out an
appropriate message if the string happens to be empty. Note that
in this particular application checking @ARGV first
would be the correct way to handle a lack of input, but we are
concerned with the subroutine here, which cannot know how, where,
or why it is being called.
undef works well in a scalar context, but is not so
good for lists. While it is perfectly possible to assign
undef to an array variable, it is confusing because
what we end up with is an array of one value, which is undefined.
If we naively tried to convert our subroutine to return a list
instead of a scalar string we might write:
sub list_files {
my $path = shift;
return undef unless defined $path; # return undef if no path
return glob "$path/*"; # return a list of files
}
Unfortunately if we try to call this function in a list context,
and do not specify a defined path, we end up with anomalous
behavior:
foreach (list_files $ARGV[0]) {
print "Found: $_\n";
# $_ == undef if path was not defined
}
If the path is undefined this will execute the loop once, print
'Found: ' and generate an uninitialized value warning. The reason
for this is that undef is not a list value, so when evaluated in
the list context of the foreach loop, it is
converted into a list containing one value, which happens to be
undefined. As a result, when the subroutine is called with an
undefined path the loop executes once, with the value of the loop
variable $_ being undefined.
In order for the loop to behave the way we intended, and not
execute even once when no results are found, we need to return an
empty list. Here's another version of list_files
that does this:
sub list_files {
my $path = shift;
return () unless defined $path; # return empty list if no path
return glob "$path/*"; # return list of files.
}
This fixes the problem we had when returning undef,
but at the cost of losing the ability to distinguish between an
undefined path and a path that happens to contain no files. What
we would really like to do is return either undef or the empty
list depending on whether a scalar or list result is required.
The wantarray function provides exactly this
information, and we cover it next.
Returning Values from Subroutines - Page 19
Professional Perl Programming
Determining and Responding to the Calling Context - Page 21
|