Perl Part I · Module 5 · Arrays and Lists
Why a List Inside a List Disappears
This chapter closes the module by naming a limitation that has been quietly behind several of its results. Putting a list inside another list does not produce a nested structure — the inner one spreads out into the outer one, and the boundary between them is gone for good. Three literals that look like completely different shapes all produce the same four elements. That is convenient for the things it makes easy, and it is a hard wall for anything that needs structure: a subroutine cannot hand back two arrays and have the caller tell which values came from which, because by the time they arrive there is only one list. The chapter ends by showing you the shape of the answer, which is the whole subject of the next module.
Foundation15 min readPerlPart IFundamentalsArraysData Structures
Part I · Module 5 · Chapter 5.12 · Why a List Inside a List Disappears
1. Three Literals, One Result
use strict;
use warnings;
my @flat = (1, 2, 3, 4);
my @nested = (1, (2, 3), 4);
my @pairs = ((1, 2), (3, 4));
printf "(1,2,3,4) : %d elements [%s]\n", scalar(@flat), join(" ", @flat);
printf "(1,(2,3),4) : %d elements [%s]\n", scalar(@nested), join(" ", @nested);
printf "((1,2),(3,4)) : %d elements [%s]\n", scalar(@pairs), join(" ", @pairs);(1,2,3,4) : 4 elements [1 2 3 4]
(1,(2,3),4) : 4 elements [1 2 3 4]
((1,2),(3,4)) : 4 elements [1 2 3 4]Three literals written to look like three different shapes. All three are four elements, flat.
The parentheses in a list do not create structure — they only group for the parser. A list inside a list is flattened into it, and nothing records that there was ever an inner list.
This is a deliberate design choice, not an accident, and it is why several things earlier in the module worked:
push @a, @moreadded the elements of@more, not@moreitself — Chapter 5.4 §4.map { ($_, $_ * 10) }turned two elements into four, because each block's two-element list flattened into the result — Chapter 5.10 §3.@row[1 .. 3]worked because the range produced a list of indexes that flattened into the subscript — Chapter 5.3 §1.
All one rule.
2. Two Arrays Lose Their Boundary
use strict;
use warnings;
my @front = ("a", "b");
my @back = ("c", "d", "e");
my @both = (@front, @back);
printf "front %d + back %d = both %d\n", scalar(@front), scalar(@back), scalar(@both);
printf "both: [%s]\n", join(" ", @both);front 2 + back 3 = both 5
both: [a b c d e]Five elements. @both has no idea that its first two came from one array and its last three from another.
For merging that is exactly right — it is the shortest way to concatenate two arrays, and no other syntax is needed. The problem arrives when you wanted to keep them apart.
And the empty list vanishes entirely
use strict;
use warnings;
my @empty = ();
my @with_literal = (1, (), 2);
my @with_array = (1, @empty, 2);
printf "(1, (), 2) : %d elements [%s]\n", scalar(@with_literal), join(" ", @with_literal);
printf "(1, \@empty, 2) : %d elements [%s]\n", scalar(@with_array), join(" ", @with_array);(1, (), 2) : 2 elements [1 2]
(1, @empty, 2) : 2 elements [1 2]Two elements, not three. An empty list contributes nothing — it does not become undef and it does not hold a place.
That is the rule behind Chapter 5.10 §3's filtering map: a block returning () contributes no element, which is why the even numbers disappeared rather than becoming undefined entries.
3. The Consequence: A Function Cannot Return Two Arrays
This is the one that matters, and it is the reason the next module exists.
use strict;
use warnings;
sub split_results {
my @passed = ("alu", "fifo");
my @failed = ("regfile");
return (@passed, @failed);
}
my @got = split_results();
printf "caller received %d element(s): [%s]\n", scalar(@got), join(" ", @got);caller received 3 element(s): [alu fifo regfile]Three elements in one list. The caller cannot tell which were which.
Nothing warned. The subroutine's intent — "here are the passes and here are the failures" — was destroyed by the return, and what arrived is a flat list that happens to have the passes first. Reconstructing the boundary requires knowing a count that the return value does not carry.
Subroutines are Module 10's subject; this one is deliberately minimal because the problem is about the data, not the function.
4. The Fork, Drawn
Two arrays into a list, and the same two kept apart
5. Only One Array on the Left, and It Must Be Last
Flattening also governs list assignment, which produces a silent bug worth knowing.
use strict;
use warnings;
my ($first, @rest) = (1, 2, 3, 4);
printf "first = %s rest = [%s] (%d)\n", $first, join(" ", @rest), scalar(@rest);first = 1 rest = [2 3 4] (3)That works and is genuinely useful: a scalar takes the first value and the array slurps everything remaining. It is the idiom for "the command and its arguments" or "the header and the rest of the rows".
Now the same thing in the other order:
use strict;
use warnings;
my (@all, $last) = (1, 2, 3, 4);
printf "all = [%s] (%d) last = [%s]\n",
join(" ", @all), scalar(@all), (defined $last ? $last : "undef");all = [1 2 3 4] (4) last = [undef]@all took all four values and $last got nothing. No warning.
The reason is flattening: the left-hand side is itself a list, and an array in it is an unbounded destination. It consumes every remaining value, so anything written after it is unreachable.
The rule: at most one array may appear on the left of a list assignment, and it must be last. The same applies to a subroutine's parameter list, which Module 10 covers.
6. What the Answer Looks Like
You cannot solve §3's problem with arrays alone. Here is the shape of the solution, so you know the gap is closable:
use strict;
use warnings;
my @front = ("a", "b");
my @back = ("c", "d", "e");
my @of_refs = (\@front, \@back);
printf "the list has %d element(s)\n", scalar(@of_refs);
printf "first points at %d element(s)\n", scalar(@{ $of_refs[0] });
printf "second points at %d element(s)\n", scalar(@{ $of_refs[1] });the list has 2 element(s)
first points at 2 element(s)
second points at 3 element(s)Two elements, each of which still knows its own length.
A reference is a scalar that refers to an array. Since it is a single scalar, it does not flatten — the list holds two scalars, and each one can be followed back to the array it came from.
use strict;
use warnings;
my @a = (1, 2);
my $r = \@a;
print "a reference prints as: $r\n";a reference prints as: ARRAY(0x7f8e2a0051c8)\@a takes a reference; @{ ... } follows one back to an array. The hexadecimal number is a memory address and differs every run, which is why a reference is not something to print in a report — it is something to follow.
That is the whole preview. The syntax, the rules, nested structures, references to hashes and the things you can build with them are Module 7's subject — the module titled for exactly this, and the one this chapter is the motivation for.
7. Why This Matters in Engineering Work
Flattening is right more often than it is wrong. Merging two lists of blocks, appending parsed fields to an accumulator, building a list of arguments — all of these want the elements and not the structure. (@a, @b) and push @all, @batch are correct and idiomatic.
It becomes a wall at exactly one point: when something has to hold several groups at once. A results table where each row has several fields. A list of failures per block. Any grouping of a grouping. Chapter 5.8 §3 hit this — it had to store parsed rows and used push @good, \@field, a reference, with a note that it was a forward pointer. That was this limitation, met in practice two chapters early.
Encoding structure into a flat list is the thing to avoid. Returning a count and then two concatenated groups works and is an agreement nothing enforces. When you reach for that, reach for Module 7's references instead.
And the slurping rule prevents a silent bug. my (@rows, $count) = f(); compiles, runs, and leaves $count undefined forever. One array, last position.
8. Common Mistakes
Expecting (1, (2, 3), 4) to nest. It is four flat elements.
Expecting ((1,2),(3,4)) to be two pairs. It is four elements.
Returning two arrays from a subroutine. The caller gets one flat list with no boundary.
Expecting an empty list to hold a place. (1, (), 2) is two elements — it contributes nothing, not undef.
Putting an array anywhere but last on the left of a list assignment. It slurps everything and the rest get undef, silently.
Using two arrays on the left. The first takes everything.
Encoding a boundary as a count in a flat return. It works and nothing enforces it. Use a reference.
Printing a reference into a report. You get ARRAY(0x...) and a different address each run.
9. Exercise
Exercise 1 — three literals. Print the element count and contents of (1,2,3,4), (1,(2,3),4) and ((1,2),(3,4)). Confirm all three agree, then state the rule in one sentence.
Exercise 2 — merge two arrays. Combine a two-element and a three-element array into one and report the count. Then say why this is the right behaviour for this particular job.
Exercise 3 — the empty list. Print the count of (1, (), 2) and of (1, @empty, 2). Say what would have had to happen for the count to be 3, and name the chapter where this rule made a filtering map work.
Exercise 4 — the two-array return. Write a subroutine returning two arrays — three passing names and two failing ones — and have the caller print what it received. Then explain in one sentence why the caller cannot separate them, and say what extra information would be needed.
Exercise 5 — slurping both ways. Run both §5 scripts. Record which variable got the values in each, confirm STDERR is empty for the broken one, and state the rule about where an array may appear.
Exercise 6 — the fragile workaround. Implement the count-plus-two-groups return from §3's callout and have the caller reconstruct both arrays. Confirm it works. Then describe one change to the subroutine that would break the caller without any diagnostic.
Exercise 7 — the preview. Reproduce §6's reference example and confirm the list has two elements while the referenced arrays have two and three. Then print one of the references directly and record what you get.
10. Summary
- A list inside a list is flattened.
(1,(2,3),4)and((1,2),(3,4))are both four flat elements. - Parentheses group for the parser; they do not create structure.
- This is why
push @a, @moreadds elements,mapcan expand, and a range works inside a subscript. One rule behind all three. - Two arrays combined lose their boundary — which is right for merging and fatal for grouping.
- An empty list contributes nothing, not
undef, which is why amapblock returning()filters. - A subroutine cannot return two arrays distinguishably. The caller receives one flat list, with no warning.
- At most one array on the left of a list assignment, and it must be last — otherwise it slurps everything and the rest are silently
undef. - A reference is a single scalar that refers to an array, so it does not flatten. That is the answer, and Module 7 is where it is taught.
That closes Module 5. You can hold several values in order, index and slice them, add and remove at either end or in the middle, tell a list from an array and know why that matters, read an array's count straight out of a condition, turn a file's lines into guarded fields, order them by any key you like, filter and transform them without loops, and answer the three questions of §5.11 in one line each.
Module 6 comes next, and it brings the other half of the answer: hashes, which look a value up by name instead of by position — the tool Chapter 5.11 §1 said was the right one for a lookup that happens more than occasionally, and Chapter 5.11 §6 named for removing duplicates.
Module 7 then takes up this chapter's limitation directly. References make a scalar that stands for a whole array or hash, which is what lets you build a list of lists, a table of rows, or a record with named fields.
Where this fits
Part of the Perl curriculum.
