Perl Part I · Module 8 · Conditional Statements
unless and Negated Conditions
unless is if with the condition negated, and nothing more. It exists because some guards read better stated positively — unless the file is readable, complain — and for that single-condition case it genuinely is clearer than a bare exclamation mark. The reason this is the module's lowest-importance chapter is that its usefulness has a sharp edge. The moment the condition contains an and or an or, reading it correctly means applying De Morgan's laws in your head, and that is the step people get wrong. Perl also accepts one construct here that almost nobody can read on sight, and it is worth seeing once so you recognise it in inherited code rather than writing it.
Foundation13 min readPerlPart IControl FlowConditionals
Part I · Module 8 · Chapter 8.2 · unless and Negated Conditions
1. It Is Exactly if (!...)
use strict;
use warnings;
my $n = 0;
unless ($n) { print "unless: n is false\n" }
if (!$n) { print "if-not: n is false\n" }unless: n is false
if-not: n is falseNo difference in behaviour, and none in performance. unless (COND) is defined as if (! COND), so everything Chapter 8.1 §4 established about what counts as false applies unchanged — 0, 0.0, "", "0", undef and an empty array are the six false things, and unless fires for exactly those.
The choice between the two forms is purely about which one reads correctly out loud.
| Reads better as | Example |
|---|---|
unless | unless (-r $path) { die "cannot read $path\n" } |
if (!...) | if (!$found) { $missing++ } |
unless | unless (@failures) { print "clean\n" } |
if (!...) | if (!defined $slack) { $unknown++ } |
The pattern is that unless wins when the condition is a positive-sounding predicate you want the absence of — readable, present, found, clean. if (!...) wins when the condition already contains a negative word like defined or a name like $missing, because unless (!defined ...) is two negatives and nobody should have to parse that.
2. unless Takes an else
use strict;
use warnings;
my $n = 5;
unless ($n > 3) { print "small\n" }
else { print "big\n" }bigIt is legal, and it is almost always the wrong thing to write. The else branch of an unless fires when the condition is true, so you have written "unless X, do A, otherwise do B" where B is the X case. Readers have to hold a negation in mind to work out which branch is which.
Every unless/else has a clearer if/else with the branches swapped:
if ($n > 3) { print "big\n" }
else { print "small\n" }The rule worth adopting: if you find yourself writing else after unless, invert it to an if. The negation was carrying no weight.
3. There Is No elsunless — but unless ... elsif Compiles
use strict;
use warnings;
my $n = 5;
unless ($n > 9) { print "a\n" }
elsunless ($n > 3) { print "b\n" }syntax error at elsunless.pl line 7, near ") {"
Execution of elsunless.pl aborted due to compilation errors.255No surprise there. The surprise is the next one.
use strict;
use warnings;
my $n = 5;
unless ($n > 9) { print "a\n" }
elsif ($n > 3) { print "b\n" }
else { print "c\n" }aThat compiled, ran, and exited 0. Perl allows elsif to follow unless.
Work out what it means. $n is 5, so $n > 9 is false, so unless ($n > 9) is true and the first branch runs. The elsif ($n > 3) — which is also true — never gets a chance, because the chain stopped at the first match exactly as Chapter 8.1 §1 described.
4. The Real Problem: and and or Inside unless
use strict;
use warnings;
my ($a, $b) = (1, 0);
printf "A=%d B=%d\n", $a, $b;
print "unless(A && B) fired\n" unless ($a && $b);
print "unless(A || B) fired\n" unless ($a || $b);
print "if(!A || !B) fired\n" if (!$a || !$b);
print "if(!A && !B) fired\n" if (!$a && !$b);A=1 B=0
unless(A && B) fired
if(!A || !B) firedTwo of the four fired, and they are the matching pair. The measurement gives you De Morgan's laws directly:
| This | Is the same as |
|---|---|
unless (A && B) | if (!A || !B) |
unless (A || B) | if (!A && !B) |
Negating a condition flips the connective. && becomes || and || becomes &&. That is the step that gets skipped, and the consequence is a guard that fires in the wrong cases.
Read unless ($ok && $complete) out loud: it is tempting to hear "unless it is OK and unless it is complete", which would be unless(A) && unless(B) — the && case. It actually means "unless both", which fires when either is missing. Those are different guards, and on data where one condition usually holds, they differ on exactly the rows that matter.
5. Negation and the Six False Values
One more interaction worth being deliberate about, because it combines this chapter with Chapter 8.1 §4.
use strict;
use warnings;
my %conf = ( retries => 0, name => "", label => "0.0" );
for my $k (qw(retries name label)) {
printf "%-8s unless-fires=%-3s defined=%-3s\n",
$k,
($conf{$k} ? "no" : "yes"),
(defined $conf{$k} ? "yes" : "no");
}retries unless-fires=yes defined=yes
name unless-fires=yes defined=yes
label unless-fires=no defined=yesAll three keys are present and defined. unless fired for two of them anyway, because 0 and "" are false — and did not fire for "0.0", because that string is true.
So unless ($conf{retries}) does not mean "unless retries was configured". It means "unless retries is a true value", and a deliberately configured 0 triggers it. The question you meant is almost certainly one of:
unless (exists $conf{retries}) { ... } # was the key ever set?
unless (defined $conf{retries}) { ... } # does it hold a real value?
unless ($conf{retries} > 0) { ... } # is it a positive number?Chapter 6.4 is the chapter on that ordering, and the reason it keeps coming back is that 0 is a legitimate configured value in almost every tool script — zero retries, zero violations, zero slack.
6. Why This Matters in Engineering Work
It is a reading skill before it is a writing skill. Inherited Perl is full of unless, including the shapes this chapter advises against. Being able to read unless ($ok && $done) correctly on sight — fires when either is missing — is worth more than any style preference.
A negated guard with and in it is a real bug shape. A pre-flight check written as unless ($have_netlist && $have_sdc) fires when either input is missing, which is correct. Written as two separate unless guards with && between their conditions reversed, it fires only when both are missing — so a run with one missing input proceeds. Both versions look reasonable in review.
And unless on a numeric field is the 0 trap again. unless ($violations) skips the report for a clean run, which is probably what you wanted, and unless ($slack) skips a path with exactly zero slack, which is not.
7. Common Mistakes
unless with && or ||. Negation flips the connective. Write the if instead.
Reading unless (A && B) as "unless A and unless B". It fires when either is false.
unless with an else. Invert to if and swap the branches.
unless ... elsif. Legal, unreadable, and can hide an unreachable branch.
Expecting elsunless to exist. It does not — syntax error, exit 255.
unless (!defined $x). A double negative. Write if (defined $x).
unless ($conf{k}) to test configuration. A configured 0 fires it. Use exists or defined.
8. Exercise
Exercise 1 — equivalence. For $n = 0, run both unless ($n) and if (!$n) and confirm both fire. Then do the same for $n = "0.0" and record that neither fires.
Exercise 2 — which reads better. Take the four rows of §1's table and write each guard both ways. For each, say in one word which form you would ship.
Exercise 3 — unless/else. Write §2's unless/else for $n = 5 and record the output. Then rewrite it as if/else with the branches swapped and confirm the output is identical.
Exercise 4 — elsunless. Write it and record the exact error and exit status.
Exercise 5 — the legal unreadable one. Write §3's unless/elsif/else for $n = 5. Record what it prints. Then work out, on paper, which values of $n reach the elsif and which reach the else. State what you find about the else.
Exercise 6 — De Morgan, measured. With $a = 1 and $b = 0, run all four guards from §4. Record which two fired. Then repeat with (0,0) and (1,1) and build the full table.
Exercise 7 — the wrong guard. Write a pre-flight check two ways: unless ($have_a && $have_b) and unless ($have_a) ... unless ($have_b) as two separate guards. Run both for all four combinations of the two flags and report where they disagree.
Exercise 8 — the zero trap. Build §5's three-key hash. For each key print whether unless fires and whether the value is defined. Then write the three better questions and say which you would use for a retries setting.
9. Summary
unless (COND)isif (! COND)— identical behaviour, identical performance. The choice is readability only.- The six false values from Chapter 8.1 are exactly what
unlessfires for. unlessearns its place in one shape: a single positive-sounding predicate, noelse, noand, noor.unlesswith anelseis legal and should be inverted to anifwith swapped branches.elsunlessdoes not exist (syntax error, exit 255), butunless ... elsifcompiles and runs — and in that shape theelsecan be unreachable with no warning.- Negating a condition flips the connective:
unless (A && B)isif (!A || !B);unless (A || B)isif (!A && !B). unless (A && B)fires when either is false, not when both are.unless ($conf{k})fires for a configured0. Useexistsordefinedfor presence.
Chapter 8.3 takes the condition and moves it to the end of the statement — the form that keeps a one-line guard on one line, and the one place Perl documents its own behaviour as undefined.
Where this fits
Part of the Perl curriculum.
