Tcl Part I · Module 4 · Operators and Expressions
The Conditional Operator
This operator picks one of two values based on a condition, inside an expression. It is the only operator in Tcl that takes three operands, and it fills a real gap: there are places where you need a value chosen on the spot and cannot stop to run a multi-line command. It is also easy to overuse. This chapter covers what it does, proves that only the chosen branch is evaluated, and gives a clear rule for when a plain if command says the same thing better.
Foundation10 min readTclPart IOperatorsConditionalReadability
Part I · Module 4 · Chapter 4.7 · The Conditional Operator
1. The Shape
set count 1
puts "1 [expr {$count == 1 ? "item" : "items"}]"
set count 3
puts "3 [expr {$count == 1 ? "item" : "items"}]"1 item
3 itemsRead it as: condition ? value if true : value if false.
The condition is read as a truth value, with the rules from Chapter 3.6 — so anything not on the accepted list is an error, not false.
2. Why It Earns Its Place
That singular-and-plural case is the best argument for the operator. The alternative is three lines and a variable to produce one word:
set count 3
if {$count == 1} {
set noun "item"
} else {
set noun "items"
}
puts "$count $noun"3 itemsBoth are correct. The first version keeps the choice where the value is used; the second introduces a variable whose only job is to carry a word to the next line.
3. Only One Branch Is Evaluated
Like && and || in Chapter 4.5, this operator does not evaluate what it does not need. The same technique proves it:
proc noisy {which} {
puts " (evaluated $which)"
return $which
}
puts "result: [expr {1 ? [noisy TRUE] : [noisy FALSE]}]" (evaluated TRUE)
result: TRUEOne message, not two. The false branch was never run.
That matters when a branch would be expensive or would fail:
set count 0
set total 50
puts [expr {$count == 0 ? "no data" : $total / $count}]no dataThe division by zero never happened, because that branch was not selected.
4. Nesting
?: associates to the right — Chapter 4.2 §4 — which is what makes a chain read sensibly:
foreach n {-5 0 7} {
puts "$n is [expr {$n < 0 ? "negative" : $n == 0 ? "zero" : "positive"}]"
}-5 is negative
0 is zero
7 is positiveEach : carries the rest of the chain, so it reads top to bottom like a list of cases.
5. Common Uses
set errors 0
set verbose 1
set name ""
puts "status: [expr {$errors == 0 ? "PASS" : "FAIL"}]"
puts "level: [expr {$verbose ? "detailed" : "summary"}]"
puts "display: [expr {$name eq "" ? "(unnamed)" : $name}]"
puts "clamped: [expr {$errors > 100 ? 100 : $errors}]"status: PASS
level: detailed
display: (unnamed)
clamped: 0All four pick a value. That is the pattern the operator is for.
The third line is the nearest Tcl has to a default-value idiom, since Chapter 4.5 §6 showed that || returns 1 or 0 rather than an operand.
6. When to Use if Instead
The rule is not about length:
?:produces a value.ifruns code.
Use ?: when you need one of two values in the middle of something else. Use if when you want one of two actions to happen.
set errors 2
expr {$errors > 0 ? [puts "there were errors"] : [puts "clean"]}there were errorsThat works, and it is the wrong tool. Nothing wanted the value; the goal was to print something. An if says that directly.
| You want | Use |
|---|---|
| one of two values, used immediately | ?: |
| one of two values, assigned to a variable | either; ?: is shorter |
| one of two actions to happen | if |
| more than two cases | if/elseif or switch |
| a branch that needs several lines | if |
| a branch that needs a comment | if |
7. Readability
Two habits keep it readable.
Keep the branches short and similar. If the two values are long or of different kinds, the line stops being scannable:
set label [expr {$ok ? "pass" : "fail"}]Parenthesise a complex condition. ?: is the loosest operator in the table, so it does not strictly need them — but a reader should not have to confirm that:
set a 5
set b 10
puts [expr {($a > 0 && $b > 0) ? "both positive" : "not both"}]both positive8. Common Mistakes
Using it for actions. §6. Use if.
Chaining more than two conditions. §4. Use switch.
Expecting both branches to run. Only the selected one is evaluated. §3.
Forgetting the condition must be a truth value. expr {"maybe" ? 1 : 0} is an error — Chapter 3.6 §4.
Writing a long expression in one branch. Compute it first and name it.
9. Exercises
Exercise 1 — singular and plural. Write one puts that prints 1 file or 4 files correctly, driven by a variable.
Exercise 2 — prove the skip. Write the §3 noisy test yourself and confirm only one message appears. Then flip the condition to 0 and confirm the other branch runs instead.
Exercise 3 — a protective branch. Write an expression that gives the average of $total over $count, or the text n/a when count is zero. Confirm it does not error for count equal to 0.
Exercise 4 — chain three. Classify a number as low, medium or high using a nested conditional. Then rewrite it with if/elseif, and say which you would keep.
Exercise 5 — pick the tool. For each, say ?: or if: choosing a filename suffix; printing a warning when a count is high; clamping a value to a maximum; running a cleanup step only on failure.
Exercise 6 — the default idiom. Given a variable that may be an empty string, produce either its value or the text (none). Then explain why expr {$name || "(none)"} does not work.
10. Remember
- The shape is
condition ? value_if_true : value_if_false, insideexpr. - It produces a value;
ifruns code. That is how to choose. - Only the selected branch is evaluated, so the other one may safely be expensive or invalid.
- It associates to the right, which makes a two-level chain read top to bottom.
- Two levels is the practical limit. Beyond that use
switchorif/elseif. - The condition must be a valid truth value, or it is an error.
- Parenthesise a complex condition, even though precedence does not require it.
- It is the closest thing Tcl has to a default-value idiom, because
||returns 1 or 0.
Next: the functions that live inside expressions.
Where this fits
Part of the Tcl curriculum.
