Skip to content
VLSI Mentor

Perl Part I · Module 8 · Conditional Statements

The Ternary Conditional Operator

Every conditional so far has been a statement: it runs one block or another. The ternary operator is a conditional that is an expression — it produces a value, so it can sit anywhere a value can sit, including inside a printf argument list or a string. That makes it the right tool for the common case where both branches of an if do nothing but assign to the same variable, which is four lines that want to be one. It has one genuinely surprising property that makes it more capable than its equivalent in most languages, and it sits at an awkward height in the precedence table, which produces two traps that give the same answer for every input and warn about nothing.

Foundation16 min readPerlPart IControl FlowConditionals

Part I · Module 8 · Chapter 8.4 · The Ternary Conditional Operator

1. A Conditional That Produces a Value

Azvya Education Pvt. Ltd.VLSI Mentor
ternary.pl
use strict;
use warnings;

for my $slack (-0.12, 0.34, 0) {
    printf "%+.2f -> %s\n", $slack, ($slack < 0 ? "FAIL" : "pass");
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
-0.12 -> FAIL
+0.34 -> pass
+0.00 -> pass

The form is COND ? VALUE_IF_TRUE : VALUE_IF_FALSE, and the whole thing is a value. That is why it can sit directly in printf's argument list, where an if statement cannot go at all.

Note the third row: a slack of exactly 0 reported pass, because 0 < 0 is false. That is the correct reading of the condition as written, and whether it is the correct policy is a different question — one that Chapter 8.6 takes up.

2. It Replaces an if Whose Branches Only Assign

Azvya Education Pvt. Ltd.VLSI Mentor
ternary-vs-if.pl
use strict;
use warnings;

my $slack = -0.12;

my $v1 = $slack < 0 ? "FAIL" : "pass";

my $v2;
if ($slack < 0) { $v2 = "FAIL" }
else            { $v2 = "pass" }

printf "ternary=%s  if=%s  same=%s\n", $v1, $v2, ($v1 eq $v2 ? "yes" : "no");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
ternary=FAIL  if=FAIL  same=yes

Same answer, one line against four — and the one-line version also lets the variable be declared and assigned together, so there is no window in which $v1 exists but holds nothing.

That is the test for whether a ternary is the right choice: do both branches do nothing but produce a value for the same destination? If yes, the ternary says so more directly. If either branch does anything else — prints, increments a counter, pushes to a list — use the if from Chapter 8.1.

Use ?:Use if
my $v = COND ? "a" : "b";either branch has side effects
inside printf/sprintf argumentseither branch has two or more statements
inside a string being builtthe condition is long
choosing a hash value or array elementthere is an elsif chain of real blocks

3. Chaining: a Classification Ladder

A ternary's false branch can be another ternary, which gives a compact band classifier.

Azvya Education Pvt. Ltd.VLSI Mentor
ternary-ladder.pl
use strict;
use warnings;

for my $s (-0.50, -0.05, 0.0, 0.50) {
    my $band = $s < -0.1  ? "critical"
             : $s < 0     ? "marginal"
             : $s == 0    ? "exactly zero"
             :              "ok";
    printf "%+.2f -> %s\n", $s, $band;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
-0.50 -> critical
-0.05 -> marginal
+0.00 -> exactly zero
+0.50 -> ok

?: is right-associative, so each : branch swallows the rest of the chain — exactly the nesting you want, with no parentheses required. Chapter 3.12 covers associativity generally.

The layout matters more than usual here. Aligning the ? and : in a column turns the chain into a readable table of condition-and-result, and the final : with no condition is the else. Written on one long line the same code is unreadable, which is the main argument people make against chained ternaries — it is a formatting problem rather than a language one.

A chain like this is equivalent to Chapter 8.1's elsif chain, and the same warning applies: the conditions are tested in order and the first match wins, so -0.50 reported critical rather than marginal even though both conditions are true for it.

4. The Ternary Is an Lvalue

This is the property that genuinely surprises people, including people who have written Perl for years.

Azvya Education Pvt. Ltd.VLSI Mentor
ternary-lvalue.pl
use strict;
use warnings;

my ($a, $b) = (1, 2);
my $pick = 1;

($pick ? $a : $b) = 99;

printf "a=%d b=%d\n", $a, $b;

$pick = 0;
($pick ? $a : $b) = 77;

printf "a=%d b=%d\n", $a, $b;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
a=99 b=2
a=99 b=77

The ternary chose which variable to assign to. With $pick true the assignment went to $a; with it false, to $b.

In C the conditional operator yields a value and cannot be assigned to. In Perl it yields the variable itself when both branches are assignable, so it can appear on the left of =.

This is occasionally genuinely useful — incrementing one of two counters without naming either twice:

Azvya Education Pvt. Ltd.VLSI Mentor
lvalue-inc.pl — one expression, two possible destinations
my ($fail, $pass) = (0, 0);

for my $slack (-0.12, 0.34, -0.5, 0, 0.1) {
    ($slack < 0 ? $fail : $pass)++;
}

printf "fail=%d pass=%d\n", $fail, $pass;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
fail=2 pass=3

Two negative values and three non-negative, counted without naming either counter twice.

More importantly, it explains a construct you will meet in older code:

Azvya Education Pvt. Ltd.VLSI Mentor
Why this parses at all
$cond ? $a = 1 : $b = 1;

That works. Measured on both branches:

Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
cond=1 -> a=1 b=0
cond=0 -> c=0 d=1

It is legal precisely because the ternary is an lvalue. It is still worth rewriting as an if, because a reader has to know this entire section to parse it, and the parenthesised form ($cond ? $a : $b) = 1; says the same thing without the ambiguity.

5. Trap One: print (...)

Azvya Education Pvt. Ltd.VLSI Mentor
print-paren.pl
use strict;
use warnings;

my $x = 0;

print ($x ? "yes" : "no"), "\n";

print "|end\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDOUT
no|end
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDERR
print (...) interpreted as function at print-paren.pl line 6.
Useless use of a constant ("\n") in void context at print-paren.pl line 6.
Azvya Education Pvt. Ltd.VLSI Mentor
Measured exit status
0

The newline never printed. The following print "|end\n" ran straight after, which is why the output reads no|end on one line.

The cause has nothing to do with the ternary. A ( immediately after print makes Perl read the parentheses as the complete argument list of a function call, so the statement means (print($x ? "yes" : "no")), "\n"; — print one thing, then evaluate "\n" in void context and throw it away.

Perl warns twice and clearly: print (...) interpreted as function names the exact problem, and Useless use of a constant ("\n") in void context names the discarded argument. Both are warnings, and the script exited 0.

Both working forms, measured:

Azvya Education Pvt. Ltd.VLSI Mentor
print-ok.pl
use strict;
use warnings;

my $x = 0;

print $x ? "yes" : "no";
print "\n";

print(($x ? "yes" : "no"), "\n");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
no
no

Either no parentheses at all, or parentheses around the whole argument list with the ternary separately bracketed inside. The habit that avoids the whole question: never put a space between print and (, and if you use parentheses, make them wrap every argument.

6. Trap Two: Concatenation Binds Tighter

This one does not warn at all, and it is the more dangerous of the two.

Azvya Education Pvt. Ltd.VLSI Mentor
concat-precedence.pl
use strict;
use warnings;

my $n = 0;

print "with parens    : " . ($n ? "set" : "zero") . "\n";

my $wrong = "count is " . $n ? "set" : "zero";
print "without parens : $wrong\n";

for my $v (0, 1, 99) {
    my $w = "count is " . $v ? "set" : "zero";
    printf "  n=%-3s -> %s\n", $v, $w;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
with parens    : zero
without parens : set
  n=0   -> set
  n=1   -> set
  n=99  -> set
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

Three different inputs, one answer, nothing on STDERR, exit 0.

. binds tighter than ?:, so "count is " . $n ? "set" : "zero" groups as:

Azvya Education Pvt. Ltd.VLSI Mentor
What Perl actually sees
("count is " . $n) ? "set" : "zero"

The condition is the concatenated string, which starts with count is and is therefore never empty — so it is always true, and the result is always "set", whatever $n holds. The $n you meant to test is still in there; it is just part of the string being tested for emptiness instead of the value being tested for truth.

7. Where a Ternary Earns Its Place

Three patterns where it is clearly the right call.

Azvya Education Pvt. Ltd.VLSI Mentor
useful.pl
use strict;
use warnings;

my @rows = (
    { block => "alu",  slack => -0.12 },
    { block => "fifo", slack =>  0.34 },
    { block => "pad",  slack =>  0     },
);

for my $r (@rows) {
    printf "%-5s %+6.2f  %-4s  %s\n",
        $r->{block},
        $r->{slack},
        ($r->{slack} < 0 ? "FAIL" : "ok"),
        ($r->{slack} < 0 ? "needs an ECO" : "no action");
}

my $n = scalar(@rows);
printf "\n%d block%s checked\n", $n, ($n == 1 ? "" : "s");

my @failing = grep { $_->{slack} < 0 } @rows;
printf "verdict: %s\n", (@failing ? "FAIL" : "PASS");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
alu    -0.12  FAIL  needs an ECO
fifo   +0.34  ok    no action
pad    +0.00  ok    no action

3 blocks checked
verdict: FAIL
  • A label inside a printf list. Two of them on one line, each parenthesised. An if cannot go here at all.
  • Pluralisation. ($n == 1 ? "" : "s") is the standard idiom and reads better than any alternative.
  • A summary verdict from a list. (@failing ? "FAIL" : "PASS") uses the array in boolean context — Chapter 8.1 §4's rule that an empty array is false.

What is not in that list is supplying a default value. $x ? $x : "none" works but evaluates $x twice and gets the 0 case wrong; $x // "none" is shorter and correct. That is Chapter 8.5's subject.

8. Why This Matters in Engineering Work

Report lines are full of them. A status column, a units suffix, a plural, a yes/no — each is a value chosen at the point of use, and a ternary puts the choice where the reader is already looking instead of four lines further up.

The concatenation trap is a real silent-failure shape. A status string built as "block " . $name . $failed ? " FAILED" : " ok" always reports failure, for every block, with no warning. The symptom — every row the same — reads like a data problem rather than a precedence problem, which is where the time goes.

And the lvalue property is worth recognising rather than using. $cond ? $a = 1 : $b = 1; appears in older scripts and parses for a reason most readers will not know. Being able to read it is useful; writing it is not.

9. Common Mistakes

print ($cond ? "a" : "b"), "\n"; The parentheses become the whole argument list and the newline is discarded. Perl warns twice and exits 0.

"text " . $n ? "a" : "b" Concatenation binds tighter, so the condition is the string and is always true. No warning.

Omitting parentheses around a ternary that is not the whole right-hand side. Parenthesise it unless it follows = directly.

Using a ternary when a branch has side effects. Use an if.

A chained ternary on one long line. Align the ? and : in a column or use an elsif chain.

$x ? $x : "default" Evaluates $x twice and mishandles 0. Use //.

Expecting the branches to be tested in parallel. A chain is ordered; the first true condition wins.

10. Exercise

Exercise 1 — the basic form. Classify -0.12, 0.34 and 0 as FAIL or pass with a ternary inside a printf. Note what the zero case reported and say whether you consider that correct policy.

Exercise 2 — against the if. Produce the same label with a ternary and with a four-line if/else, then compare the two results with eq and print whether they match.

Exercise 3 — the ladder. Build §3's four-band classifier and run it on -0.50, -0.05, 0.0 and 0.50. Then swap the first two conditions and report what -0.50 becomes.

Exercise 4 — the lvalue. Reproduce §4 exactly, including the second assignment after flipping $pick. Then write ($slack < 0 ? $fail : $pass)++; in a loop over three slack values and print both counters.

Exercise 5 — the print trap. Write print ($x ? "yes" : "no"), "\n"; and record STDOUT, both STDERR warnings, and the exit status. Add a print "|end\n"; after it and explain the combined output. Then fix it twice — once with no parentheses, once with parentheses around the whole argument list.

Exercise 6 — the concatenation trap. Write my $w = "count is " . $v ? "set" : "zero"; inside a loop over 0, 1 and 99. Record all three results and confirm STDERR is empty. Then write out the grouping Perl actually used, and fix it with parentheses.

Exercise 7 — the useful three. Reproduce §7: a status label and an action label in one printf, a pluralised count, and a verdict from an array in boolean context. Then set the array to empty and confirm the verdict flips.

Exercise 8 — when not to. Take a ternary whose true branch also increments a counter, and show why it cannot be written as a ternary. Rewrite it as an if and state the rule in one sentence.

11. Summary

  • COND ? A : B is an expression that produces a value, so it can sit in a printf list, a string, or a hash value, where an if cannot.
  • Use it when both branches only produce a value for the same destination. Any side effect means use an if.
  • It is right-associative, so ?: chains nest naturally into a classification ladder — and like an elsif chain, the first true condition wins.
  • The ternary is an lvalue. ($pick ? $a : $b) = 99; assigns to whichever variable the condition selects — measured on both branches.
  • print ($cond ? "a" : "b"), "\n"; discards the newline. The parentheses become print's whole argument list; Perl warns twice and exits 0.
  • . binds tighter than ?: — "text " . $n ? "a" : "b" tests the concatenated string, which is never empty, so it returns "a" for every input with no warning at all.
  • Parenthesise a ternary unless it is the whole right-hand side of an assignment.
  • Do not use $x ? $x : "default" — it evaluates $x twice and mishandles 0. Chapter 8.5 has the right tool.

Chapter 8.5 covers that tool: how && and || stop as soon as the answer is known, and how to use that both to guard a lookup and to supply a fallback.

Where this fits

Part of the Perl curriculum.