Perl Part I · Module 10 · Subroutines and Arguments
wantarray: Returning One Value or a List
Chapter 10.6 showed that the caller decides what a return means. wantarray lets the subroutine find out what was decided, and return something shaped to match — a list of failing blocks when the caller wants a list, the number of them when the caller wants one value. Used in the one place it genuinely fits, that is a neat interface. Used more widely it produces a subroutine whose behaviour cannot be described without describing its caller, which is the opposite of what a reusable subroutine is for. This is the only chapter in the module the curriculum marks Medium rather than High or Critical, and the measurement in section four is why.
Advanced13 min readPerlPart ISubroutines
Part I · Module 10 · Chapter 10.7 · wantarray: Returning One Value or a List
1. Three Answers, Not Two
use strict;
use warnings;
sub probe {
my $w = wantarray;
printf " wantarray = %s\n",
(defined $w ? ($w ? "1 (list)" : "empty string (scalar)") : "undef (void)");
return 1;
}
print "called in list :\n"; my @a = probe();
print "called in scalar :\n"; my $b = probe();
print "called in void :\n"; probe();called in list :
wantarray = 1 (list)
called in scalar :
wantarray = empty string (scalar)
called in void :
wantarray = undef (void)wantarray is not a boolean. It has three possible values, and the name — which suggests a yes-or-no question about arrays — is actively misleading:
| Caller's context | wantarray returns | Test with |
|---|---|---|
list — my @x = f(), my ($x) = f() | 1 (true) | if (wantarray) |
scalar — my $x = f(), if (f()) | "" (false, defined) | if (defined wantarray && !wantarray) |
void — f(); on its own | undef | if (!defined wantarray) |
The ordering of tests therefore matters, and it is the same ordering Chapter 6.4 established for hash values: truth first, then definedness.
sub context_of {
return "list" if wantarray; # 1
return "scalar" if defined wantarray; # "" — defined but false
return; # undef — void
}called in list context : list
called in scalar context : scalarNote what the void branch can do: nothing useful. There is nowhere for a void-context return value to go, so detecting void context is only worth it to skip work — a subroutine that would build an expensive list can return early if nobody is going to look at it. That is a real optimisation and a rare one.
2. The One Good Use
use strict;
use warnings;
sub failing_blocks {
my @bad = ("alu", "regf");
return wantarray ? @bad : scalar(@bad);
}
my @list = failing_blocks();
my $count = failing_blocks();
printf "list context : %s\n", join(" ", @list);
printf "scalar context : %d\n", $count;list context : alu regf
scalar context : 2Both call sites read naturally. my @list = failing_blocks() gives the blocks; my $count = failing_blocks() gives how many. One subroutine, two sensible interfaces, no second function name.
What makes this case work is that the two answers are about the same question. "Which blocks failed" and "how many blocked failed" are the list and the size of the same thing, so a reader who guesses either way guesses right.
This is also exactly what Perl's own builtins do. grep in list context gives the matches and in scalar context gives the count — Chapter 5.10 §5 measured it — and reverse gives a reversed list or a reversed string. So the pattern is idiomatic when it mirrors that: the scalar answer is a summary of the list answer.
Note that scalar(@bad) is written explicitly rather than relying on return @bad collapsing. Chapter 10.6 §4 argued for that, and here it is doubly worth it: the whole point of the subroutine is that it is deliberate about context, so leaving one branch to an implicit rule undercuts it.
3. Writing It Correctly
Three details that are easy to get wrong.
return wantarray ? @bad : scalar(@bad); # fine — one callwantarray is cheap, so calling it twice is not a performance problem — but assigning it once makes the three-state logic readable:
sub f {
my $want = wantarray;
return unless defined $want; # void: do nothing
return @list if $want; # list
return scalar(@list); # scalar
}wantarray reports the context of the current subroutine call, not of any enclosing expression. So this does not work:
sub outer { return inner() } # inner sees outer's RETURN context
sub inner { return wantarray ? "list" : "scalar" }In that arrangement inner's wantarray reflects the context of return inner() inside outer, which Perl propagates from outer's own caller — so it happens to work here. But insert anything between, and it stops:
sub outer { my @tmp = inner(); return @tmp }Measured, with both forms of outer called in scalar context:
my $x = direct() -> SCALAR
my $x = viatemp() -> 1The second answer is worth a second look: it is not "LIST". my @tmp = inner() forced list context, so inner returned the string "LIST" into a one-element array — and then return @tmp in the caller's scalar context gave that array's count. Chapter 10.6 §1's rule, stacked on top of the wantarray branch.
That is the first sign of the real problem: a wantarray-based subroutine's behaviour depends on code that is not in it, so adding a temporary variable in a caller changed the answer from a string to the number 1.
4. Why This Chapter Is Marked Medium
5. Where You Will Actually Meet It
Three places wantarray turns up in code you did not write:
Accessor-style helpers that return a list or its size:
sub ports { return wantarray ? @{ $self->{ports} } : scalar @{ $self->{ports} } }Error-reporting subroutines that return details or a flag:
sub validate {
my @errors = check_all();
return wantarray ? @errors : !@errors; # list of errors, or a boolean OK
}That second one inverts the truth value between contexts. Measured, both error counts in both contexts:
errors=0 : list gives 0 item(s), scalar gives TRUE
errors=2 : list gives 2 item(s), scalar gives falseWith no errors the list is false and the scalar is true. So if (validate()) means "valid" and my @e = validate(); if (@e) means "invalid" — both are reasonable readings of the same subroutine name, and they are exact opposites. If you meet one, read it twice.
Context-preserving wrappers, which are the one place wantarray is close to necessary:
sub timed {
my ($fn) = @_;
my $start = time;
if (wantarray) {
my @r = $fn->();
warn "took " . (time - $start) . "s\n";
return @r;
}
my $r = $fn->();
warn "took " . (time - $start) . "s\n";
return $r;
}Without the wantarray branch, a wrapper forces one context on everything it wraps — so a wrapped array-returning function would always hand back a count. This is the pattern that justifies wantarray existing, and it is also why wrappers are more verbose in Perl than the thing they wrap.
6. Why This Matters in Engineering Work
You will read it before you write it. Accessor helpers in inherited flow libraries use it constantly, and knowing it is three-state rather than boolean is what lets you read the defined test correctly.
The count-instead-of-a-row failure is a real shape. my $row = get_rows(); is a natural thing to write, gives 3, and the script then treats a number as a row. The subroutine is not at fault, which is why the investigation goes to the wrong place.
And two names beat one context-sensitive name for anything other scripts call. failing_blocks() and failing_count() are self-describing at the call site and need no test matrix over contexts.
7. Common Mistakes
Treating wantarray as a boolean. It has three values; void context returns undef.
Testing if (wantarray) for scalar context. That branch is false for both scalar and void. Use defined wantarray.
A scalar branch returning a different kind of thing from the list branch. The caller cannot guess; use two subroutines.
Returning a list of errors in list context and a success flag in scalar context. The truth value inverts between the two.
Testing only one context. The untested path is the one callers hit by accident.
Assuming a wrapper preserves context. It does not unless it branches on wantarray.
Relying on implicit collapse in the scalar branch. Write scalar(@list) explicitly.
8. Exercise
Exercise 1 — three states. Write a subroutine that prints what wantarray returned, then call it in list, scalar and void context. Record all three and say which one defined distinguishes.
Exercise 2 — the correct test order. Write context_of returning "list", "scalar" or nothing. Call it in all three contexts and confirm each. Then swap the two tests and record what scalar context now reports.
Exercise 3 — the good case. Write failing_blocks from §2 and call it both ways. Confirm the list and the count.
Exercise 4 — the trap. Write rows from §4 and call it as my ($first) = rows() and my $maybe = rows(). Record both. Confirm STDERR is empty and the exit status is 0.
Exercise 5 — the test that misses it. Write a test for rows that only uses list context and confirm it passes. Then break the scalar branch — return "oops" instead of the count — and confirm the test still passes.
Exercise 6 — two names. Rewrite rows as two subroutines with fixed contracts. Rewrite both call sites and say which version you would put in a shared library.
Exercise 7 — the inverting validator. Write §5's validate returning errors or !@errors. Call it with no errors and with two, in both contexts, and build the full four-cell table. Point at the two cells that read as opposites.
Exercise 8 — the wrapper. Write a wrapper that calls a code reference and reports how long it took, without the wantarray branch. Call it wrapping an array-returning subroutine in list context and record what you get. Then add the branch and record it again.
9. Summary
wantarrayhas three values, not two:1in list context, the empty string in scalar context, andundefin void context.- So test truth first, then definedness —
if (wantarray)thenif (defined wantarray).if (wantarray)alone is false for both scalar and void. - Void context can only be used to skip work, since there is nowhere for a return value to go.
- The good use is when the scalar answer summarises the list answer — the blocks and their count. That mirrors
grepandreverse, and reads correctly either way. - Write
scalar(@list)explicitly in the scalar branch rather than relying on implicit collapse. - The cost is that the subroutine's contract moves to the call site. Measured:
my $maybe = rows()returned3where the caller meant one row — correctly, by design, with no warning. - Tests must cover both contexts, because the untested path is the one callers reach by accident.
- A wrapper does not preserve its wrapped call's context unless it branches on
wantarray— the one case where it is close to necessary. - Two names beat one context-sensitive name for anything shared.
Chapter 10.8 covers the other half of returning: handing back a container rather than a list, so two things do not arrive joined — and what a bare return gives in each context.
Where this fits
Part of the Perl curriculum.
