perlmeditation
kyle
<p>Using [doc://wantarray], a sub can tell what context it was called in so it can behave differently depending on whether its return value will be ignored, put into a scalar, or put into a list. An idiom I've seen a number of times uses [doc://wantarray] to decide whether to [doc://return] an array or an array reference.
<c>
sub get_x {
my @x = qw( stuff );
return wantarray ? @x : \@x;
}
</c>
<p>This way, you can either write "<c>@foo = get_x()</c>" or "<c>$foo = get_x()</c>", and it will "just work".
<p>I am against this practice.
<readmore>
<p><b>Which way "just works"?</b>
<p>At $work right now, you can find both of these:
<c>
return wantarray ? @x : \@x;
</c>
<c>
return wantarray ? @x : $x[0];
</c>
<p>I wonder what will happen when one of those two programmers has to work on the other's code.
<p><b>I didn't think context mattered</b>
<p>...or <b>I didn't know I changed the context</b>.
<p>Imagine refactoring this:
<c>
my $dewdrop_description = get_droplet();
$dewdrop_description->{substance} = 'water';
$dewdrop_description->{molecular_array} = get_moles( 'water' );
$dewdrop_description->{temperature} = 37;
</c>
<p>We want to eliminate the repetition. Make it like this:
<c>
my $dewdrop_description = {
%{ get_droplet() },
substance => 'water',
molecular_array => get_moles( 'water' ),
temperature => 37,
};
</c>
<p>Nice, right? Yes, except now it's broken because at the end of <c>get_moles()</c> we have:
<c>
return wantarray ? @moles : \@moles;
</c>
<p>Our straight-forward refactoring has changed the context of <c>get_moles()</c> from scalar to list, and the result is that <c>$dewdrop_description</c> gets polluted with all manner of extra stuff. If you're lucky, <c>get_moles()</c> returns an even number of items, and you have [doc://warnings] on, and you are told "Odd number of elements in anonymous hash". If you're not lucky, there's no warning, and an odd index element from <c>get_moles()</c> clobbers a key that <c>get_droplet()</c> returned.
<p><b>Don't surprise me</b>
<p>I'm often looking at code already written for some example of how to use things. Given a "<c>$scalar = x()</c>", I expect that <c>x()</c> returns a scalar. I know very well that's not necessarily true and that context can have effects far beyond its source. Nevertheless, it generally does not occur to me that maybe this scalar-returning behavior is conditional.
<p>A sub that returns more than one value can have unpredictable behavior in [doc://scalar] context. What you get depends on whether the scalar context applies to an array or a list. If a sub returns a list, you can't just apply a scalar context to it and know what it will do without looking inside the sub to see what's to the right of [doc://return]. In light of this, it almost seems merciful to use [doc://wantarray] to lay down the law. A consistent use across code might actually clear some things up.
<p>Still, I am not in favor of this.
<p><b>Be consistent</b>
<p>Always return the same thing.
<p>If your caller wants to stick your list in a scalar instead of an array, make it build the array reference itself.
<p>If the list you return is so massive that copying it all is a burden, return an array reference. If the caller wants to copy it into an array anyway, it can dereference your return value and suffer the consequences.
<p><b>On the defense</b>
<p>I've seen it suggested that every assignment from a sub call should be with a list context.
<c>
my ($scalar) = why();
</c>
<p>This way, if it's giving you a scalar, you get it. If it's giving you a list, you get the first element. If it's giving you an array, you get the first element. It's about as consistent as you can get without looking into <c>why()</c>.
<p>I'm not quite to that point yet, but I can see where it's coming from.
<p><b>A good use of wantarray</b>
<p>I think it's a good idea to use [doc://wantarray] to check for void context. That can be used for run-time programming mistakes like calling an accessor in void context.
<c>
sub get_or_set {
my $self = shift;
if ( ! @_ && ! defined wantarray ) {
die "accessor called in void context";
}
$self->{stuff} = shift @_ if @_;
return $self->{stuff};
}
</c>
<p>This will catch the case where you don't know you've passed in nothing.
<c>
my @x = (); # but I thought it was qw( foo ) !
get_or_set( @x ); # set value
</c>
<p><b>"Better" versions of wantarray</b>
<p>I'll note without comment that there are a couple of modules out there that do what [doc://wantarray] does <em>and more</em>, if you're into that kind of thing.
<ul>
<li>[mod://Contextual::Return]—recommended by [TheDamian] in <i>Perl Best Practices</i></li>
<li>[mod://Want]</li>
</ul>
</readmore>