Tcl Part I · Module 8 · Conditions and Control Flow
if, elseif, else
You have been reading if since Module 1 and writing it since Module 3, and it has never been explained. This chapter pays that debt, and it starts somewhere that matters: in most languages if is syntax, built into the grammar, with rules of its own. In Tcl it is a command — an ordinary one, with no special status, taking a condition and one or more script bodies as its arguments. Once you see that, several things that look arbitrary stop being arbitrary: why elseif is one word, why the braces are not optional decoration, and why an if can appear on the right-hand side of a set.
Foundation11 min readTclPart IControl flowifConditions
Part I · Module 8 · Chapter 8.1 · if, elseif, else
1. The basic form
set count 3
if {$count == 0} {
puts "nothing to do"
} elseif {$count == 1} {
puts "one item"
} else {
puts "$count items"
}3 itemsA condition in braces, a body in braces, optional elseif pairs, an optional else. That is the shape you will write ninety per cent of the time.
2. It is a command
Chapter 2.1 established that a Tcl line is a command name followed by words. if is no exception:
if {$count == 0} {puts zero} else {puts other}otherCount the words: if, {$count == 0}, {puts zero}, else, {puts other}. Five words. The first is the command name and the rest are its arguments — the braces group each script into a single word, exactly as Chapter 2.8 described.
Nothing about if is special to the parser. You can prove it the way Chapter 1.1 did, by replacing it:
rename if original_if
proc if {args} {
puts "my own if was called with [llength $args] arguments"
uplevel 1 original_if $args
}
if {1 == 1} {puts "and the body still ran"}
rename if {}
rename original_if ifmy own if was called with 2 arguments
and the body still ranA piece of language syntax cannot be renamed. A command can.
3. The condition is an expr
The first argument is evaluated by expr, so everything from Module 4 applies unchanged:
set width 16
set mode fast
if {$width > 8 && $mode eq "fast"} {
puts "wide and fast"
}
if {$width in {8 16 32}} {
puts "a supported width"
}wide and fast
a supported width&&, eq, in — all operators from Module 4, not special if features. There is no separate "condition language" to learn.
4. A non-boolean condition is an error
This is the behaviour Chapter 3.6 warned about, and here is where it bites:
if {"hello"} { puts yes }expected boolean value but got "hello"An empty string is the same:
set name ""
if {$name} { puts yes }expected boolean value but got ""Tcl does not have a notion of "truthy". A condition must produce something it recognises as a boolean: 1/0, yes/no, true/false, on/off (any case), or any number — where zero is false and everything else is true.
foreach v {1 0 yes no true false on off -1 0.0 00} {
puts [format "%-6s %s" $v [expr {$v ? "true" : "false"}]]
}1 true
0 false
yes true
no false
true true
false false
on true
off false
-1 true
0.0 false
00 falseNote 00: it is a number, and that number is zero, so it is false. And note what is absent from the list — there is no entry for "hello" or "", because those are not booleans at all.
5. elseif is one word
set x 1
if {$x == 0} {
puts a
} elseif {$x == 1} {
puts b
}bSplit it into two words and the command cannot make sense of its arguments:
if {$x == 0} { puts a } else if {$x == 1} { puts b }wrong # args: extra words after "else" clause in "if" commandelse takes exactly one argument — the body. Giving it if, {$x == 1} and {puts b} is three, so if reports extra words. The message is precise once you read if as a command counting its arguments.
6. if returns a value
Because it is a command, if returns something — the result of whichever body ran:
set answer [if {1} {expr {6 * 7}} else {expr {0}}]
puts $answer42If no branch runs and there is no else, the result is the empty string:
set answer [if {0} {expr {6 * 7}}]
puts "'[set answer]'"''This is occasionally useful and mostly a curiosity. The readable way to choose between two values is the conditional operator from Chapter 4.7:
set count 1
set label [expr {$count == 1 ? "item" : "items"}]
puts "$count $label"1 itemReach for if when you are choosing between actions, and for ? : when you are choosing between values.
7. Chaining, and the order of tests
elseif branches are tested in order, and the first match wins — the rest are never evaluated:
proc classify {n} {
if {$n < 0} {
return "negative"
} elseif {$n == 0} {
return "zero"
} elseif {$n < 10} {
return "small"
} else {
return "large"
}
}
foreach n {-5 0 3 99} {
puts [format "%4d %s" $n [classify $n]]
} -5 negative
0 zero
3 small
99 largeBecause the first match wins, ordering overlapping conditions from most specific to least specific is not a style preference — it is the difference between working and not:
proc wrong {n} {
if {$n < 10} {
return "small"
} elseif {$n < 0} {
return "negative"
}
return "large"
}
puts [wrong -5]small-5 is indeed less than 10, so the first branch claimed it and the negative branch is unreachable. Nothing warns you about this.
8. A worked example
Validating a setting, with the branches ordered deliberately:
proc check_width {w} {
if {![string is integer -strict $w]} {
return "not a number: '$w'"
} elseif {$w <= 0} {
return "must be positive"
} elseif {$w > 1024} {
return "unreasonably large"
} elseif {$w & ($w - 1)} {
return "not a power of two"
}
return "ok"
}
foreach w {16 0 abc 2048 12 ""} {
puts [format "%-6s %s" "'$w'" [check_width $w]]
}'16' ok
'0' must be positive
'abc' not a number: 'abc'
'2048' unreasonably large
'12' not a power of two
'' not a number: ''The string is integer -strict test comes first for a reason: every branch after it does arithmetic, and arithmetic on abc would be an error rather than a message. The -strict is the flag from Chapter 3.5 that makes the empty string fail the test rather than pass it.
9. Exercises
Exercise 1. How many words does Tcl see in if {$a} {puts x} else {puts y}, and what is each one?
Exercise 2. What is the error from if {$x == 0} {puts a} else if {$x == 1} {puts b}, and why?
Exercise 3. Predict each: if {"0"} {...}, if {"00"} {...}, if {"off"} {...}, if {"nope"} {...}.
Exercise 4. Write grade returning A for 90 and above, B for 80–89, C for 70–79, and F below 70. Then say what goes wrong if the branches are ordered from lowest to highest.
Exercise 5. What does set r [if {0} {expr 1}] put in r?
Exercise 6. Rewrite this with the conditional operator and say why that is better here:
if {$n == 1} { set unit "cycle" } else { set unit "cycles" }Answers
1. Five: if, {$a}, {puts x}, else, {puts y}. The first is the command name; the other four are its arguments.
2. wrong # args: extra words after "else" clause in "if" command. else expects exactly one argument — a body — and it was handed three (if, the condition, and the body). elseif is a single word precisely so this does not arise.
3. "0" is false. "00" is false — it is a number, and that number is zero. "off" is false. "nope" is an error: expected boolean value but got "nope".
4.
proc grade {n} {
if {$n >= 90} {
return A
} elseif {$n >= 80} {
return B
} elseif {$n >= 70} {
return C
}
return F
}
foreach n {95 85 75 50} { puts "$n -> [grade $n]" }95 -> A
85 -> B
75 -> C
50 -> FOrdered the other way — >= 70 first — every score of 70 or more would return C, because the first matching branch wins and the later, stricter tests would never be reached.
5. The empty string. No branch ran and there is no else, so if has nothing to return.
6.
set unit [expr {$n == 1 ? "cycle" : "cycles"}]The code is choosing between two values for one variable, not between two actions. The operator says that directly, assigns once, and cannot accidentally set different variables in the two branches.
10. Remember
ifis a command, not syntax. It takes a condition and one or more script bodies as ordinary arguments, and it can be renamed.- The condition is evaluated by
expr, so every operator from Module 4 works and there is no separate condition language. - A non-boolean condition is an error, not false —
expected boolean value but got "hello", and the same for the empty string. - Booleans are
1/0,yes/no,true/false,on/off, and any number —"00"is false because it is numerically zero. elseifis one word.else ifgiveswrong # args: extra words after "else" clause.thenis optional and legal, and almost nobody uses it.ifreturns the value of the branch that ran, or the empty string when nothing ran.- Branches are tested in order and the first match wins, so overlapping conditions must go from most specific to least.
- Use
ifto choose between actions and? :to choose between values.
Where this fits
Part of the Tcl curriculum.
