Returns and Perl Subroutines - Returning Failure (
Page 4 of 4 )
Use a bare return to return failure.
Notice that each final return statement in the examples of the previous guideline used a return keyword with no argument, rather than a more-explicit return undef.
Normally, relying on default behaviour is not best practice. But in the case of a
return
statement, relying on the default return value actually prevents a particularly nasty bug.
The problem with returning an explicit return undef is that—contrary to most people’s expectations—a returned undef isn’t always false.
Consider a simple subroutine like this:
use Contextual::Return;
sub guesstimate
{
my ($criterion) = @_;
my @estimates;
my $failed = 0;
# [Acquire data for specified criterion]
return undef if $failed;
# [Do guesswork based on the acquired data]
# Return all guesses in list context or average guess in scalar context...
return (
LIST { @estimates }
SCALAR { sum(@estimates)/@estimates; }
);
}
The successful return values are both fine, and completely appropriate for the two contexts in which the subroutine might be called. But the failure value is a serious problem. Since
guesstimate()
specifically tests for calls in list context, it’s obvious that the subroutine is expected to be called in list contexts:
if (my @melt_rates = guesstimate('polar melting')) {
my $model = Std::Climate::Model->new({ polar_melting => \@melt_rates });
for my $interval (1,2,5,10,50,100,500) {
print $model->predict({ year => $interval })
}
}
But if the guesstimate() subroutine fails, it returns a single scalar value: undef. And in a list context (such as the assignment to @melt_rates), that single scalar undef value becomes a one-element list: (undef). So @melt_rates is assigned that one-element list and then evaluated in the overall scalar context of the if
condition. And in scalar context an array always evaluates to the number of elements in it, in this case 1
. Which is true.
Oops!*
What should have happened, of course, is that
guesstimate()
should have returned a failure value that was false in whatever context it was called, i.e.,
undef
in scalar con
text and an empty list in list context:
if ($failed) {
return (
LIST { () }
SCALAR { undef }
);
}
But that’s precisely what a
return
itself does when it’s not given an argument: it returns whatever the appropriate false value is for the current call context. So, by always using a bare
return
to return a “failure value”, you can ensure that you will never bring about the destruction of the entire planetary ecosystem because of an expectedly true
undef
.
Meanwhile, Chapter 13 presents a deeper discussion on the most appropriate ways to propagate failure from a subroutine.
* Yep, that’s the sound of alarm bells you’re hearing.
† And if that sample happens to be an integer, then $found will be assigned a numeric value, exactly as expected. It will be the wrong numeric value, but hey, at least that will make the bug much more interesting to track down.
* They’d be the “edge-cases”, except that, in this instance, they’re conceptually in the middle of possibilities.
* And here “Oops!” means: the
if
block executes despite the failure of
guesstimate()
to acquire any meaningful data. So, when the climate model requests a numerical polar melting rate, that
undef
is silently converted to zero. This dwimmery causes the model to show that polar melting rates have absolutely no connection to world climate in general, and to rising ocean levels in particular. So mankind can happily keep burning fossil fuels at an ever-greater rate, secure in the knowledge that it has no effect. Until one day, the only person left is Kevin Costner. On a raft.