Perl Part I · Module 6 · Hashes
Turning a Hash Into a List, and Back
A hash used where a list is wanted flattens into alternating keys and values, and a list of alternating keys and values assigned to a hash becomes one. That single reversible conversion is behind three things worth having: copying a hash, merging two of them with a defined precedence, and the way Perl gives you named arguments to a subroutine without any special syntax for them. It is also the direct sequel to Chapter 5.12 — because flattening is what makes passing a hash possible, and the same flattening is why passing two of them gives the receiving code one merged hash with no way to tell which key came from where.
Intermediate14 min readPerlPart IHashesData StructuresIdioms
Part I · Module 6 · Chapter 6.10 · Turning a Hash Into a List, and Back
1. The Conversion Runs Both Ways
use strict;
use warnings;
my %original = (alu => "PASS", fifo => "FAIL", decoder => "PASS");
my @flat = %original;
my %rebuilt = @flat;
printf "hash keys : %d\n", scalar(keys %original);
printf "flat items : %d\n", scalar(@flat);
printf "rebuilt keys : %d\n", scalar(keys %rebuilt);
printf "identical : %s\n",
(join(",", map { "$_=$original{$_}" } sort keys %original) eq
join(",", map { "$_=$rebuilt{$_}" } sort keys %rebuilt) ? "yes" : "no");hash keys : 3
flat items : 6
rebuilt keys : 3
identical : yesThree keys became six list items — each pair contributing its key and then its value — and six items assigned back to a hash gave the same three pairs.
- A hash in list context flattens to alternating keys and values. That is the same rule Chapter 5.12 established for arrays, applied to pairs.
- A list assigned to a hash is read as alternating keys and values, which is exactly how Chapter 6.2 §1 built one in the first place.
So the conversion is lossless in both directions, and Chapter 6.2 was already using half of it.
2. Copying a Hash
use strict;
use warnings;
my %original = (retries => 3, timeout => 60);
my %copy = %original;
$copy{retries} = 99;
printf "original retries : %d\n", $original{retries};
printf "copy retries : %d\n", $copy{retries};original retries : 3
copy retries : 99my %copy = %original; flattens the right-hand hash to a list and builds a new hash from it, so the two are independent. Changing one does not affect the other.
That independence holds for a flat hash of simple values, which is every hash in this module. It stops holding the moment the values are references to other structures, because then the copy duplicates the references rather than what they point at — which is Chapter 7.13's subject, and the reason that chapter exists.
3. Merging, and Why Later Wins
use strict;
use warnings;
my %defaults = (retries => 3, timeout => 60, verbose => 0);
my %overrides = (timeout => 120, jobs => 8);
my %settings = (%defaults, %overrides);
for my $k (sort keys %settings) {
printf "%-8s %s\n", $k, $settings{$k};
}jobs 8
retries 3
timeout 120
verbose 0Both hashes flattened into one list, and that list built one hash. timeout appeared twice — 60 from the defaults and 120 from the overrides — and the later one won, because building a hash from a list assigns each pair in order and a repeated key overwrites, exactly as Chapter 6.3 §3 measured.
So the overwrite behaviour that was a hazard in §3 of that chapter is the mechanism here. (%defaults, %overrides) is the idiomatic way to apply overrides to defaults, and the order of the two is the whole meaning of the line:
| Written as | Means |
|---|---|
(%defaults, %overrides) | overrides win — almost always what you want |
(%overrides, %defaults) | defaults win, silently ignoring the overrides |
Getting those the wrong way round produces a script that ignores its own configuration and reports nothing.
4. Named Arguments to a Subroutine
This is what the canonical index means by "how hashes get passed into and out of subroutines", and it is the chapter's main payoff.
use strict;
use warnings;
sub describe {
my %args = @_;
printf "got %d named argument(s): %s\n", scalar(keys %args),
join(" ", map { "$_=$args{$_}" } sort keys %args);
}
describe(block => "alu", mode => "fast");
my %opts = (block => "fifo", mode => "slow", retries => 3);
describe(%opts);got 2 named argument(s): block=alu mode=fast
got 3 named argument(s): block=fifo mode=slow retries=3Two calls, two forms, one subroutine. The mechanism is the conversion:
- At the call site,
block => "alu", mode => "fast"is just a four-item list —=>is a comma that quotes, from Chapter 6.2 §2. And%optsflattens into the same shape. - Inside,
@_holds that list —@_is the array of arguments, which Module 10 covers — andmy %args = @_;reads it as alternating keys and values.
So Perl has no named-argument syntax. It has flattening plus =>, and my %args = @_; is the one line that turns them into named arguments.
Why that is worth doing:
- The call site documents itself.
describe(block => "alu", mode => "fast")says what each value is;describe("alu", "fast")requires the reader to find the subroutine. - Order stops mattering. Callers can pass the arguments in any order.
- Optional arguments are natural. A caller omits what it does not need, and the subroutine checks with
exists— Chapter 6.4.
With defaults
Combining §3 and §4 gives the standard shape:
use strict;
use warnings;
sub run_block {
my %args = (retries => 3, verbose => 0, @_);
printf "block=%-6s retries=%d verbose=%d\n",
$args{block}, $args{retries}, $args{verbose};
}
run_block(block => "alu");
run_block(block => "fifo", retries => 10);block=alu retries=3 verbose=0
block=fifo retries=10 verbose=0my %args = (retries => 3, verbose => 0, @_); puts the defaults first and @_ last, so anything the caller passed overrides a default — §3's later-wins rule doing the work. One line, and both calls get sensible values for everything they did not mention.
That is the idiom to remember from this chapter.
An odd number of arguments warns
use strict;
use warnings;
sub describe {
my %args = @_;
printf "keys: %s\n", join(" ", sort keys %args);
}
describe(block => "alu", "mode");keys: block modeOdd number of elements in hash assignment at odd-args.pl line 5.Three arguments, so one pair is incomplete — and this is Chapter 6.2 §3's diagnostic, now firing on a subroutine call. The warning names the line inside the subroutine, not the call site, because that is where the hash assignment is.
That is worth knowing when you see it: Odd number of elements in hash assignment pointing at a my %args = @_; line means a caller passed the wrong number of arguments, and you have to find which one.
5. Two Hashes Cannot Be Passed Separately
use strict;
use warnings;
sub inspect {
my %all = @_;
printf "the sub received %d key(s): %s\n", scalar(keys %all), join(" ", sort keys %all);
}
my %first = (a => 1);
my %second = (b => 2);
inspect(%first, %second);the sub received 2 key(s): a bOne hash with two keys. The subroutine cannot tell that it was called with two separate hashes, because by the time @_ exists there is only a flat list.
This is Chapter 5.12 §3's finding in the other direction. There, a subroutine could not return two arrays distinguishably; here it cannot receive two hashes. One rule, both directions, and no warning in either.
6. Why This Matters in Engineering Work
Named arguments make a subroutine survive its own growth. A function taking four positional arguments gets a fifth, and every call site has to be checked for ordering. With my %args = @_; a new argument is additive and old calls keep working — which matters for a utility that several scripts call.
Defaults-plus-overrides is one line. my %args = (retries => 3, @_); is the whole mechanism for optional configuration, and it reads in the order it applies.
Config merging from several sources. Site defaults, then project settings, then command-line overrides: my %cfg = (%site, %project, %cmdline); applies them in precedence order, left to right, with the last winning. The order of that list is the precedence rule, which makes it readable and reviewable.
And the flat list must not be printed unsorted. A hash flattened into a file or a log line has a pair order that changes between runs, so the output is not reproducible — Chapter 6.6 again. Sort by key on the way out.
7. Common Mistakes
Printing join(",", %h) directly. The pair order changes between runs.
Getting the merge order backwards. (%overrides, %defaults) silently ignores the overrides.
Expecting a hash copy to be deep. It is independent for simple values only; references are shared — Chapter 7.13.
Passing two hashes to one subroutine. They arrive merged, with no warning.
Reading Odd number of elements at a my %args = @_; line as a bug in the subroutine. It means a caller passed an odd number of arguments.
Putting @_ before the defaults. my %args = (@_, retries => 3); makes the default override the caller.
Using positional arguments for a subroutine with more than two or three. Named arguments cost one line and remove the ordering question.
8. Exercise
Exercise 1 — the round trip. Flatten a three-key hash to an array, report both counts, rebuild a hash from it and confirm the contents match.
Exercise 2 — the unstable flat list. Print join(",", %h) for a four-key hash five times and record the lines. Then print it sorted by key five times and record those.
Exercise 3 — copy independence. Copy a two-key hash, change one key in the copy, and print both. Confirm the original is untouched.
Exercise 4 — merge both ways. Merge a defaults hash and an overrides hash that share one key, in both orders. Print both results and say which line you would commit.
Exercise 5 — named arguments. Write a subroutine using my %args = @_; that prints its arguments sorted. Call it once with a literal list and once with a hash variable, and confirm both work.
Exercise 6 — defaults. Extend Exercise 5 so two arguments have defaults. Call it supplying neither, then one, then both, and confirm the right values appear each time.
Exercise 7 — the odd call. Call your subroutine with an odd number of arguments. Record the warning and which line it names, then say where the actual mistake is.
Exercise 8 — two hashes. Pass two separate hashes to one subroutine and have it report what it received. Say how many hashes it saw and name the chapter that established why.
9. Summary
- A hash in list context flattens to alternating keys and values; a list assigned to a hash is read the same way. The conversion is lossless both ways.
- The pair order is hash order, so a flattened hash is not stable between runs — sort by key before printing it.
my %copy = %original;is an independent copy for simple values.(%defaults, %overrides)merges with the later key winning, which makes the list order the precedence rule.my %args = @_;gives named arguments, because=>is a comma and the call's list flattens.my %args = (defaults, @_);applies defaults first and lets callers override — the idiom to remember.- An odd argument count warns at the
my %args = @_;line, but the mistake is at the call site. - Two hashes passed together arrive merged, with no way to tell them apart — Chapter 5.12's rule in the other direction, and the last of the four limitations Module 7 exists to lift.
Chapter 6.11 closes the module by putting the two containers side by side: when order is what matters, and when lookup by name is.
Where this fits
Part of the Perl curriculum.
