Professional Perl | 18
[next] |
Professional Perl
Returning Values from Subroutines
Subroutines can return values in one of two ways, either implicitly, by reaching the end of their block, or explicitly, through the use of the return statement.
If no explicit return statement is given, then the return value of a subroutine is the value of the last statement executed (the same as for ordinary bare blocks). For example, the string 'implicit return value' is returned by the following simple subroutine because it is the last (and in this case, only) statement in the subroutine:
sub implicit_return {
my $string = "implicit return value";
}
Or even just:
sub implicit_return {
"implicit return value";
}
To explicitly define the return value we use the return statement, return takes an expression as its argument, and returns its value to the caller:
sub explicit_return {
return "explicit return value";
}
It follows from this that it is never actually necessary to use return when passing back a value from the last statement in the subroutine. However, it is good practice, to indicate that we know what we are doing and are aware of what the return value is. If a subroutine does not have an explicit return, the implication is that it does not return a value of use.
There is nothing to stop us from putting several return statements into the same subroutine. Whichever return statement is encountered first will cause the subroutine to exit with the value of the supplied expression, aborting the rest of the subroutine. The following simple subroutine illustrates this:
sub list_files {
$path = shift;
return "" unless defined $path; # return an empty string if no path
return join(', ', glob "$path/ * "); # return comma separated string
}
Here we have used two return statements. The first returns the undefined value if we fail to supply a pathname for the subroutine to look at. The second is only reached if a defined (but not necessarily valid or existent) path is supplied. We could call this subroutine with code that looks like this:
if (my $files = list_files ("/path/to/files")) {
print "Found $files \n";
}
Multiple return statements are a convenient way to return values from a subroutine as soon as the correct value has been computed, but for large subroutines they should be used with caution. Many programming problems stem from over-complex subroutines that have more than one return in them, causing a crucial piece of code to be skipped in some cases and not others. This is often a sign that the subroutine is too large to be easily maintained and should be split into smaller functional blocks. Otherwise, it is better to funnel the execution of the subroutine to just one return statement at the end, or otherwise make it very clear in the source where all the exits are.
The list_files subroutine above works, but it is a little clumsy. It does not allow us to distinguish between an undefined path and a path on which no files were found. It also returns the files found as a string rather than a list, which would have been more useful. The first of these we can fix by using the undefined value to indicate an error. The second we can fix by returning a list, or more cunningly, by detecting the calling context and returning a scalar or list value as appropriate. We will cover each of these in turn.
Returning the Undefined Value
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.
Contents |
[next] |
Created: April 18, 2001
Revised: April 18, 2001