Perl Part I · Module 5 · Arrays and Lists
Arrays in Scalar Context
Chapter 5.6 established that an array asked for a single value reports how many elements it holds. This chapter turns that into the most useful idiom in the module. Because the count is what you get, and because zero is the only count that is false, putting an array directly into a condition asks exactly the question you usually want — did anything end up in here — with no comparison, no length function and no chance of an off-by-one. The work is in knowing which surroundings ask for one value, because the answer is not obvious: interpolating an array into a string gives you its elements while concatenating one gives you its count, and those two lines look nearly identical.
Foundation14 min readPerlPart IFundamentalsArraysContextIdioms
Part I · Module 5 · Chapter 5.7 · Arrays in Scalar Context
1. The Count, for Free
use strict;
use warnings;
my @failures = ("fifo", "regfile");
printf "scalar() : %d\n", scalar(@failures);
printf "scalar assign : %d\n", do { my $n = @failures; $n };
printf "numeric compare : %s\n", (@failures > 1 ? "more than one" : "one or none");
printf "arithmetic : %d\n", @failures * 10;scalar() : 2
scalar assign : 2
numeric compare : more than one
arithmetic : 20Four different surroundings, all asking for one value, all getting 2.
scalar(@a)asks explicitly — Chapter 2.10's operator.my $n = @ais a scalar assignment, so the right-hand side is asked for one value.@a > 1is a numeric comparison, and Chapter 3.5 established that>wants numbers.@a * 10is arithmetic, which also wants a number.
The do { ... } in the second line is only there to fit a declaration into a printf argument; ignore it as scaffolding.
2. Which Surroundings Ask for One Value
This is the part worth having as a reference, because guessing produces Chapter 2.9's class of bug.
| Surrounding | Context | @a with 3 elements gives |
|---|---|---|
scalar(@a) | scalar | 3 |
my $n = @a; | scalar | 3 |
if (@a), unless (@a), while (@a) | scalar | 3, which is true |
@a == 0, @a > 2 | scalar | 3 |
@a + 1 | scalar | 4 |
"text " . @a | scalar | text 3 |
"@a" in a string | list | a b c |
print @a | list | the elements |
my ($x) = @a; | list | $x is the first element |
push @b, @a; | list | three elements appended |
sort @a, grep ... @a, join ",", @a | list | the elements |
The pattern behind the table: anything that wants a single value imposes scalar context; anything that wants several imposes list context. That is Chapter 2.9's rule, and the table is just that rule applied to the surroundings you will actually meet.
The pair that proves it
Two string operations on the same array, one line apart:
use strict;
use warnings;
my @failures = ("fifo", "regfile");
print "interpolated : @failures\n";
print "concatenated : " . @failures . "\n";interpolated : fifo regfile
concatenated : 2The same array. Two string operations. The elements, then the count.
- Interpolation puts the array's elements into the string, space-separated — because a string is being built from several values.
- Concatenation with
.wants one value on each side, by Chapter 3.3 §1, so the array gives its count.
Nothing warns about either. Both are correct for what was written, and the difference between them is whether the array is inside the quotes or outside them.
3. The Idiom: if (@failures)
use strict;
use warnings;
my @failures = ("fifo", "regfile");
my @clean = ();
if (@failures) {
printf "%d test(s) failed: %s\n", scalar(@failures), join(", ", @failures);
}
unless (@clean) {
print "clean run — nothing to report\n";
}2 test(s) failed: fifo, regfile
clean run — nothing to reportif (@failures) reads as "if anything failed", and it is exactly that. The condition imposes scalar context, the array gives its count, and the only count that is false is 0 — by Chapter 2.8.
Compare the alternatives:
| Written as | Works? | Comment |
|---|---|---|
if (@failures) | yes | idiomatic; reads as the question |
if (scalar(@failures)) | yes | explicit; redundant here but harmless |
if (scalar(@failures) > 0) | yes | correct and verbose |
if ($#failures >= 0) | yes | correct and easy to get wrong |
if ($#failures) | no | wrong — see below |
That last row is a real bug. $#failures is -1 for an empty array and 0 for a one-element array, by Chapter 5.2 §3. Since -1 is true and 0 is false, if ($#failures) reports true for empty and false for exactly one element — wrong in both directions, which is the same double failure index produced in Chapter 4.5 §2.
Use if (@array). It is shorter and it is right.
4. An Array Holding One False Value Is True
This is the property that makes the idiom safe, and it surprises people.
use strict;
use warnings;
my @empty = ();
my @one_zero = (0);
my @one_blank = ("");
printf "empty : %s\n", (@empty ? "true" : "false");
printf "holds (0) : %s\n", (@one_zero ? "true" : "false");
printf "holds (\"\") : %s\n", (@one_blank ? "true" : "false");empty : false
holds (0) : true
holds ("") : trueOnly the empty array is false. An array holding a single 0 is true, and so is one holding a single empty string.
The reason is the whole point of the chapter: if (@a) tests the count. For both of those arrays the count is 1, and 1 is true. The contents were never consulted — which is worth stating plainly, because 0 and "" are precisely the values Chapter 2.8 listed as false, and if the idiom tested contents these two rows would read false.
That is the behaviour you want. A list of measured values containing a single 0 is not an empty list — something was measured and the answer was zero. if (@readings) correctly says "there are readings"; the value of those readings is a separate question.
Contrast this with the trap from Chapter 5.4 §3, where while (my $job = shift @work) did test the content and stopped early on a 0. Testing the array tests the count; testing an element tests the element. Those are different questions and the array form is almost always the one you want.
5. print Is List Context
use strict;
use warnings;
my @a = ("x", "y", "z");
print "print \@a -> ";
print @a;
print "\n";
print "print scalar(\@a) -> ", scalar(@a), "\n";print @a -> xyz
print scalar(@a) -> 3print @a wrote the elements, with no separator between them — xyz, not x y z. That is list context: print takes a list of things to output.
Two details worth separating:
- The elements, not the count. Chapter 2.10 §6 listed "omitting
scalar()insideprint" as a mistake, and this is it with the array properly introduced. - No separator. Interpolation inside a string —
print "@a"— joins with spaces.print @awith the array outside the quotes joins with nothing. The space in the interpolated form comes from a special variable that Module 13 covers; the practical rule is to usejoinwhen the separator matters.
use strict;
use warnings;
my @a = ("x", "y", "z");
print @a, "\n";
print "@a\n";
print join(", ", @a), "\n";xyz
x y z
x, y, zThree forms, three outputs. The third is the one to write when the output matters, because it says what it does.
6. Why This Matters in Engineering Work
if (@failures) is the shape of almost every reporting decision. Did anything fail, was anything skipped, were there violations. One expression, no comparison, nothing to get wrong — and it pairs with the Chapter 5.4 §5 accumulator that filled the array.
while (@work) is the right queue loop for the reason §4 gives: it stops when the queue is empty rather than when an element happens to be false. That is the fix Chapter 5.4 §3 pointed forward to.
The concatenation hazard is a real source of wrong report lines. "found " . @matches . " matches" prints a number where a reader might reasonably expect names, or — worse — prints a count in a sentence written as though it listed items. join and scalar remove the ambiguity, and in a line that someone will act on, removing ambiguity is worth four characters.
And if ($#array) is worth recognising as a bug when you inherit it, because it is subtly wrong rather than obviously broken: it behaves correctly for every array of two or more elements, so it survives testing on realistic data and fails on the empty and single-element cases — which are exactly the boundary cases a clean run and a one-failure run produce.
7. Common Mistakes
Using . where you meant interpolation. "blocks: " . @names gives the count.
Using if ($#array). True when empty, false with one element. Use if (@array).
Expecting if (@a) to test the contents. It tests the count, so (0) is true. That is the desired behaviour.
Expecting print @a to insert separators. It does not; "@a" inserts spaces and join inserts what you ask for.
Writing if (scalar(@a) > 0). Correct but verbose. if (@a) says the same thing.
Assuming an array in a string always gives elements. Inside the quotes, yes. Outside and concatenated, no.
8. Exercise
Exercise 1 — four scalar contexts. With a four-element array, get the count via scalar(), a scalar assignment, a numeric comparison and arithmetic. Confirm all four agree.
Exercise 2 — the proving pair. Print the same array once interpolated inside a string and once concatenated with .. Record both outputs and say which gave the count.
Exercise 3 — the idiom. Write a script with a populated @failures and an empty @clean that reports the failures and confirms the clean run, using if and unless with no comparison operators at all.
Exercise 4 — break it with $#. Test an empty array and a one-element array with if ($#a). Record both results, state which is wrong and why, and name the chapter where index produced the same double failure.
Exercise 5 — one false element. Build three arrays: empty, holding 0, and holding "". Print whether each is true. Then explain in one sentence why two of them are true, and why that is the behaviour you want for a list of measurements.
Exercise 6 — three print forms. Print a three-element array with print @a, print "@a" and print join(", ", @a). Record all three and say which you would use in a report a colleague reads.
Exercise 7 — fix a report line. The line print "skipped: " . @skipped . " of " . @total . "\n"; is meant to list the skipped tests. Say what it actually prints, then rewrite it so it lists the names and reports the counts correctly.
9. Summary
- An array in scalar context gives its element count. No function call needed.
- Scalar context comes from:
scalar(), a scalar assignment,if/unless/whileconditions, numeric comparisons, arithmetic and concatenation with.. - List context comes from: interpolation inside a string,
print's argument list, a parenthesised left-hand side,push,sort,grep,join. - Interpolating gives the elements; concatenating gives the count. Same array, one line apart, neither warns.
if (@failures)is the idiom — it asks "is there anything here" and the only false count is0.if ($#array)is a bug: true when empty, false with one element.- An array holding a single
0is true, because the count is 1. Testing the array is not testing its contents. print @awrites the elements with no separator. Usejoinwhen the separator matters.
Chapter 5.8 applies the whole module so far to the job it exists for: turning a line of a data file into an array of fields, and handling the line that has fewer fields than you expected.
Where this fits
Part of the Perl curriculum.
