Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set count 3

if {$count == 0} {
    puts "nothing to do"
} elseif {$count == 1} {
    puts "one item"
} else {
    puts "$count items"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3 items

A 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if {$count == 0} {puts zero} else {puts other}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
other

Count 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 if
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my own if was called with 2 arguments
and the body still ran

A 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if {"hello"} { puts yes }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
expected boolean value but got "hello"

An empty string is the same:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set name ""
if {$name} { puts yes }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach v {1 0 yes no true false on off -1 0.0 00} {
    puts [format "%-6s %s" $v [expr {$v ? "true" : "false"}]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1      true
0      false
yes    true
no     false
true   true
false  false
on     true
off    false
-1     true
0.0    false
00     false

Note 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set x 1
if {$x == 0} {
    puts a
} elseif {$x == 1} {
    puts b
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
b

Split it into two words and the command cannot make sense of its arguments:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if {$x == 0} { puts a } else if {$x == 1} { puts b }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
wrong # args: extra words after "else" clause in "if" command

else 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set answer [if {1} {expr {6 * 7}} else {expr {0}}]
puts $answer
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
42

If no branch runs and there is no else, the result is the empty string:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set answer [if {0} {expr {6 * 7}}]
puts "'[set answer]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
''

This is occasionally useful and mostly a curiosity. The readable way to choose between two values is the conditional operator from Chapter 4.7:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set count 1
set label [expr {$count == 1 ? "item" : "items"}]
puts "$count $label"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 item

Reach 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  -5  negative
   0  zero
   3  small
  99  large

Because 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc wrong {n} {
    if {$n < 10} {
        return "small"
    } elseif {$n < 0} {
        return "negative"
    }
    return "large"
}
puts [wrong -5]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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]" }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
95 -> A
85 -> B
75 -> C
50 -> F

Ordered 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

  • if is 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.
  • elseif is one word. else if gives wrong # args: extra words after "else" clause.
  • then is optional and legal, and almost nobody uses it.
  • if returns 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 if to choose between actions and ? : to choose between values.

Where this fits

Part of the Tcl curriculum.