Perl Part I · Module 6 · Hashes
exists vs defined vs Truth
There are three questions you can ask about a hash key and they are not the same question. Was this key ever set at all? If it was, does it hold a real value or nothing? And if it holds a value, is that value true? Most code asks the third by accident, because putting a hash element straight into an if is the shortest thing to write — and the third question is the one you almost never mean. In this trade the numbers that matter are error counts, violation counts and failure counts, where the good answer is zero, and zero is false. So the obvious test reports a block with no errors in exactly the same way it reports a block that was never tested, which is the most expensive confusion a report can contain.
Foundation16 min readPerlPart IFundamentalsHashesDebuggingContext
Part I · Module 6 · Chapter 6.4 · exists vs defined vs Truth
1. Three Questions
| Test | Asks | Answers no when |
|---|---|---|
exists $h{k} | was this key ever set? | the key is not in the hash |
defined $h{k} | does it hold a real value? | the key is absent or its value is undef |
if ($h{k}) | is that value true? | absent, undef, 0, "0" or "" |
They are nested: anything that fails exists also fails defined, and anything that fails defined also fails the truth test. But the reverse is not true, and the gaps between them are where the bugs live.
Chapter 2.7 established definedness and Chapter 2.8 established truth. This chapter is those two plus a third question that only containers have — because only a container can be asked whether a member is there at all.
2. The Full Matrix
use strict;
use warnings;
my %h = (
pass => "PASS",
zero => 0,
zero_str => "0",
double_0 => "00",
empty => "",
emptied => undef,
);
# 'absent' is deliberately never set
printf "%-10s %-8s %-8s %s\n", "key", "exists", "defined", "true";
printf "%-10s %-8s %-8s %s\n", "-" x 10, "-" x 8, "-" x 8, "-" x 4;
for my $k (qw(pass zero zero_str double_0 empty emptied absent)) {
printf "%-10s %-8s %-8s %s\n", $k,
(exists $h{$k} ? "yes" : "no"),
(defined $h{$k} ? "yes" : "no"),
($h{$k} ? "yes" : "no");
}key exists defined true
---------- -------- -------- ----
pass yes yes yes
zero yes yes no
zero_str yes yes no
double_0 yes yes yes
empty yes yes no
emptied yes no no
absent no no noSeven states, three tests, and the three tests disagree on four of the seven rows.
Read the rows that matter:
zero— the value is0. It exists, it is defined, and it is not true. This is the row that causes real damage.double_0— the value is"00". It is true, while"0"is not."00"is not one of Chapter 2.8's false values, so it is true like any other non-empty string. Nobody predicts this one.empty— the empty string exists and is defined and is false.emptied— set toundef, so it exists but is not defined. That is Chapter 6.3 §4's state.absent— never set. The only row whereexistssays no.
3. The Three Questions, Drawn
One key holding zero, asked three different questions
4. The Hazard, Measured
use strict;
use warnings;
my %errors = (alu => 0, fifo => 7);
print "using if (\$errors{\$block}):\n";
for my $block (qw(alu fifo decoder)) {
if ($errors{$block}) {
printf " %-8s has errors: %d\n", $block, $errors{$block};
}
else {
printf " %-8s reported as NO DATA\n", $block;
}
}
print "\nusing exists:\n";
for my $block (qw(alu fifo decoder)) {
if (exists $errors{$block}) {
printf " %-8s measured %d error(s)\n", $block, $errors{$block};
}
else {
printf " %-8s was never measured\n", $block;
}
}using if ($errors{$block}):
alu reported as NO DATA
fifo has errors: 7
decoder reported as NO DATA
using exists:
alu measured 0 error(s)
fifo measured 7 error(s)
decoder was never measuredRead the first block of output. alu and decoder got the same report, and they are completely different situations:
aluwas tested and had zero errors — the best possible outcome.decoderwas never tested — an unknown.
The truth test cannot tell them apart, because 0 is false and an absent key is false, and if only sees false.
The second block gets both right, because exists asks the question that actually distinguishes them.
5. Which Test to Use
The question to ask yourself is what would it mean for this to be missing?
| You want to know | Use |
|---|---|
| did this test run at all | exists $h{$k} |
| did it produce a value | defined $h{$k} |
| is the value non-zero / non-empty | if ($h{$k}) — deliberately |
| the value, or a default if absent | $h{$k} // $default |
use strict;
use warnings;
use v5.10;
my %errors = (alu => 0, fifo => 7, decoder => undef);
for my $block (qw(alu fifo decoder regfile)) {
if (!exists $errors{$block}) {
printf "%-8s : not run\n", $block;
}
elsif (!defined $errors{$block}) {
printf "%-8s : ran, no count recorded\n", $block;
}
elsif ($errors{$block} == 0) {
printf "%-8s : clean\n", $block;
}
else {
printf "%-8s : %d error(s)\n", $block, $errors{$block};
}
}alu : clean
fifo : 7 error(s)
decoder : ran, no count recorded
regfile : not runFour distinct outcomes from four distinct tests, in the order exists → defined → value. That ordering matters: testing defined first would merge "not run" into "no count recorded", and testing the value first would hit the undef case and warn.
Note the value test is == 0, not if (...). Once you know the value is defined, comparing it is clearer than relying on truth — and Chapter 3.5 is why it is == and not eq.
// is the defaulting form
use strict;
use warnings;
use v5.10;
my %errors = (alu => 0);
printf "alu with // : %s\n", $errors{alu} // "missing";
printf "alu with || : %s\n", $errors{alu} || "missing";
printf "zzz with // : %s\n", $errors{zzz} // "missing";alu with // : 0
alu with || : missing
zzz with // : missing// tests definedness, so the real zero survives. || tests truth, so it replaces the zero with the default — Chapter 3.4 §4's measurement, now with a hash element instead of a scalar.
For a hash of counts, // is the operator. // needs Perl 5.10 or later, as Chapter 3.4 established.
6. exists Works on Arrays Too
use strict;
use warnings;
my @a = (1, undef, 3);
for my $i (0, 1, 2, 5) {
printf "a[%d] : exists %-3s defined %s\n", $i,
(exists $a[$i] ? "yes" : "no"),
(defined $a[$i] ? "yes" : "no");
}a[0] : exists yes defined yes
a[1] : exists yes defined no
a[2] : exists yes defined yes
a[5] : exists no defined noPosition 1 holds undef — it exists and is not defined. Position 5 is past the end — it does not exist.
That makes exists the test that distinguishes Chapter 5.1 §5's silent out-of-range read from a legitimately undefined element. It is used far less on arrays than on hashes, because a position is rarely "missing" in a meaningful way — an array of four fields has positions 0 to 3 and that is that. The hash case is the one worth the habit.
7. Why This Matters in Engineering Work
A status report has three outcomes, not two. Clean, dirty, and never ran. A test that only distinguishes two of them will put one of the three in the wrong column, and §4 shows which: the clean ones get reported as unknown.
Zero is the good news and it is false. That one sentence is the whole chapter. Any construct reading a count as a boolean — if ($h{$k}), $h{$k} || $default, grep { $h{$_} } — treats a clean result as an absent one.
"Ran but produced nothing" is a real third state. A test that crashed before writing its count, a tool that emitted a row with an empty field. exists without defined catches it, and it deserves its own line in a report because it is an infrastructure problem rather than a design one.
And exists is the guard for a first-wins build. Chapter 6.3 §3's duplicate-row case had four possible policies; "keep the first" is $h{$k} = $v unless exists $h{$k}; — which works correctly even when the first value was 0 or undef, where a truth or definedness guard would not.
8. Common Mistakes
Putting a hash element straight into an if to test presence. It tests truth. A zero or empty value reads as absent.
Using defined when you mean exists. They differ for a key explicitly set to undef — a real state, as Chapter 6.3 §4 showed.
Assuming "0" and "00" behave alike. "0" is false; "00" is true.
Defaulting a count with ||. It replaces a measured zero. Use //.
Testing the value before checking defined. A comparison against undef warns.
Testing defined before exists when you need to distinguish "not run" from "no value". The order has to be exists, then defined, then the value.
Expecting a plain read to create a key. It does not; only nested access does, which is Chapter 7.9's subject.
9. Exercise
Exercise 1 — build the matrix. Reproduce §2's script with all seven states and print the three-column table. Then write one sentence for each row where the three tests disagree.
Exercise 2 — the "00" row. Test "0", "00", "0.0", " " and "0E0" for truth. Report which are true and name the chapter that lists Perl's false values.
Exercise 3 — the hazard. Build a hash with one block at 0 errors and one at 3, then report on three blocks — including one absent — using if ($h{$k}). Record which two blocks got identical output and explain why that is wrong.
Exercise 4 — fix it with exists. Rewrite Exercise 3 so all three blocks get distinct, correct reports. Confirm the clean block is reported as clean.
Exercise 5 — four outcomes. Reproduce §5's four-way if/elsif chain with a hash containing a zero, a non-zero, an undef and one absent key. Then move the defined test before the exists test and record which two outcomes merge.
Exercise 6 — // against ||. For a key whose value is 0, print the result of defaulting with // and with ||. Say which you would use for an error count and why in one clause.
Exercise 7 — first wins. Build a hash from four rows where one key repeats, keeping the first value rather than the last. Use exists as the guard, then confirm it still works when the first value is 0.
10. Summary
- Three questions, not one:
existsasks whether the key was ever set,definedwhether it holds a real value, andif ($h{$k})whether that value is true. - They disagree on four of seven states — see §2's matrix.
0,"0"and""exist and are defined and are false."00"is true.- A key set to
undefexists but is not defined; an absent key fails both. if ($errors{$block})reports a clean block exactly as it reports an untested one, and in this trade clean is the common case — so the bug fires on most rows.- Order the tests
exists→defined→ value when all three outcomes matter. - Default a count with
//, never||, or a measured zero is replaced. - A plain read does not create a key; only nested access does.
existsworks on array elements too, distinguishing anundefelement from one past the end.
Chapter 6.5 moves from asking about one key to walking through all of them — the two functions that give you every key and every value, and a third that keeps a hidden position marker and is easier to misuse than it looks.
Where this fits
Part of the Perl curriculum.
