Perl Gem, context confusion

From Bilbo
Jump to: navigation, search

2010-01-24

The output of this would surprise even many experienced Perl hackers.

#!/usr/bin/perl

use strict;
use warnings;

sub fn {
        my $one = 1;
        my $two = 2;
        my @three = ("a", "b", "c", "d");
        return $one, $two, @three;
}

my $scalar = fn();
print $scalar, "\n";

One reasonable guess at the output would be "1". Indeed if you add parenthesis as in "my ($scalar) = fn();", that is correct because assigning lists in list context matches up elements from left to right, discarding trailing values. A more experienced but also incorrect guess would be "d", the last element in the flattened list. Although it is true that in scalar context a list returns its last element, that does not mean the last element of the list is evaluated in list context. The output is actually "4". The last element of the list is an array (not a list), which is evaluated in scalar context, returning the number of elements in the array.

This is similar to how a function lacking an explicit return value returns the result of the last statement. Also the context of the return value is always determined by the caller, which can make the final statement in a function behave differently depending on the caller.

On a related pitfall, omitting the return statement in a function is a great way to obscure problems. This may allow a caller to rely on a value you didn't intend to expose by returning, thus breaking the caller's code when you rearrange the function without (intentionally) changing your API. Worse however, the final statement of your function may behave wildly differently depending on whether the caller sets void, scalar, or list context. So make a habit of specifying an empty "return;" at the ends of functions that do not return a meaningful value.

Often it is good to control the return value tightly depending on whether the caller expects a scalar or list. For example, this would clear up any confusion in the above example, and behave exactly the same in all cases:

sub fn {
        my $one = 1;
        my $two = 2;
        my @three = ("a", "b", "c", "d");
        return $one, $two, @three if wantarray;
        return @three;  # scalar or void context.
}

See also wantarray (which should've been named wantlist).