if

if, a built-in Tcl command, conditionally evaluates scripts.

Synopsis

if expr1 ?then? body1 elseif expr2 ?then? body2 elseif ... ?else? ?bodyN?

See Also

expr
expr problems with int
If we had no if
if 0 {

Documentation

official reference

Description

if is the standard command for performing a test and executing a script depending on the result.

In Tcl, all control structures are implemented as commands, including if.

if returns the result of whichever script it conditionally executes, or, if it decides not to execute a script, the empty string.

expr arguments are evaluated as expressions, in exactly the same way expr arguments are evaluated.

set y [if {$x} {list a} {list b}]

$y will be a if $x evaluates to true, or b if $x evaluates to false.

The ? operator of expr does similar things.

set y [expr {{foo} eq {bar} ? yes : no}]

LV: Just for completeness sake:

set x 0
set y [if {$x} {list a}] ;# -> a

AMG: Be careful about using list for this purpose, because under some circumstances it must change its argument in order to produce a valid single-element list. list a may return a, but list "a b" returns {a b} (it formatted a b as a list item by adding braces to the result). See my comment on the return page for safer ways to do this. I suggest using lindex a instead of list a as the identity function above.

Examples

DKF:

I happen to believe that the following if examples illustrate good style, and this is because the styles minimise the number of backslashes (a problem if you insist on putting open braces at the start of a line out of some belief that Tcl has to look like C) and maximise the comprehensibility and readability at a local level (where you need an otherwise clause, always mark it with else, and where the start of the then clause is not on the same line as the condition that guards it, always mark it with then - it is better to have a multi-line condition than it is to have one monster that wraps round several times.)

# Standard simple if
if {some condition} {
    # Note that the open-brace for the body is on the same line
    # as the if-command and condition.
 
    some conditionally executed script...
}

# Standard simple if with else clause
if {some condition} {
    some conditionally executed script.
} else {
    some script to execute if the condition is not satisfied.
}

# Standard multi-test if
if {first condition} {
    thing to do if the first condition succeeds
} elseif {second condition} {
    thing to do if the first fails, but the second condition succeeds
} else {
    what to do if none of the conditions match - this one is optional,
    but typically good practice to anticipate unexpected responses
}

# Handling complex (i.e. long) conditions
if {
    this condition stretches over
    multiple lines
} then {
    thing to do if the condition succeeds
}

Using in and ni:

if {$pattern in [list case1 case2 case3 case4]} { ... }

provides the functionality of lsearch -exact, while

if {$pattern ni [list case1 case2 case3 case4]} { ... }

provides the inverse (pattern is not in the supplied case list).

Coding Style

If you want to put the test and its corresponding (true) script on one line, and then have a large else spread out over several lines, you need to add a line-continuation backslash to that first line or Tcl consider the if complete and throw an error when it tries to run else as a command. Example:

if {condition} {script} \
else {
    longer
    script
}

This is a consequence of Tcl's basic syntax rules. It is usually considered to be idiomatic to use some other style that minimizes backslash usage.

Short-circuiting operators

Sample of boolean operators:

set a 1
set b stuff
if { $a == 1 ||
    $b eq {nonsense}} {
    puts found
}

This demonstrates that one can test two (or more) expressions within expr1. In the second test, a Tcl command is executed and the result is tested for true or false. In both cases, as soon as the entire expr1 can be determined true or false, processing stops. This is sometimes called short circuiting. The good news is that it permits you to write things like:

if a particular variable exists &&
    a tcl command called using that function (variable) returns true
    do something special
else
    we get here because the particular variable didn't exist.

The bad news, as such, is that you have to be certain that you use the right boolean connector between the parts. The && requires that all parts of the if expr1 are true. The || requires only that one of them be true.

Intuitive logical operators

The following procedure allows the use of and and or in place of && and ||, respectively. Caveat: this works so far only on the first condition:

proc If {cond args} {
    regsub -all { and } $cond {\&\&} cond
    regsub -all { or } $cond {||} cond
    uplevel 1 if [list $cond] $args
} ;#RS

PYK 2014-06-07: To do this properly would require a full parser for the language of expr.

for vs. expr

Question: Does the evaluation of expr1 by if differ from how expr evaluates its arguments?

Answer: No, expr in if is processed exactly the same as an argument to expr

DKF: They use the same parser and bytecode generator. Main difference is that expr concatenates its arguments before parsing, but if requires a single argument.

Brace your expr-essions

See Brace your expr-essions.

FW: Why do if expressions use braces? Dollar signs and brackets are processed, obviously, and everything appears to work a lot like a quoted string. What gives? Just sugar? Admittedly, a lot of coders would be scared off by quoted conditionals, but I'm curious.

RS PYK: A string enclosed in curly braces is passed almost completely verbatim to its command. $, [, \, and whitespace get left alone. expr itself will then interpret the string. Since expr understands $, [, \ in the same way that Tcl does, the results are often similar, with difference that the subtle effects of double evaluation are minimized. Also, performance will improve, since Tcl can then byte-compile the expression. Bracing is also important to protect any " characters from being parsed away by Tcl before expr can see them. You can omit braces if the condition is a single string constant (no special characters, and no whitespace) without them.

The first three will often have the same result, though the first two may be slower, and are subject to double substitution:

if $foo==1     {puts YES} ;#(1)
if "$foo == 1" {puts YES} ;#(2)
if {$foo == 1} {puts YES} ;#(3)
if ""==""      {puts YES} ;#(4)
ERROR: extra characters after close-quote

On the other hand, braces can't be used when the operator is a variable value:

set op ==
if "$foo $op 1" {puts YES}

So: you may do without curlies around if conditions, but they are recommended and sometimes required - not by if, but by the Tcl way.

FW: Ah, the ignorance of youth. Reading back on that is funny. I was thinking of conditionals in general, but I didn't know the general way to refer to it or realize it's the same thing as expr.

== vs. string compare

A discussion of when in an if to use == vs. using some other way of comparing two variables needs to be made either here or on its own page string compare ....

AMG: Use == when comparing numeric values and eq when comparing string values. If you need more advanced options like case-insensitive, substring, or glob comparison, use string equal or string match.


Andreas Leitgeb, comp.lang.tcl, 2002-08-07, pointed out that

if 1 $cmd

is clearly faster then

eval $cmd

See Many ways to eval for the ensuing discussion.


RJM: can anybody explain why string comparisons in if using == or eq fail when one or both strings are constants? For example

if {$a eq abc} ...

The interpreter reports "variable references require preceding $". I'd expect to use quoting only when there are whitespaces in the string. Indeed I need quoting the constant string here.

RS: The little language of expressions (used in if, for, while, expr) is not fully the same as Tcl. The difference here is that string constants have to be explicitly grouped with "" or {}, which is not necessary in Tcl itself. Other differences are:

  • operators cannot be substituted from variables
  • functions are called like f($x,$y) instead of f $x $y
  • whitespace between operands and operators is not required

elseif in Various Languages

AMG: I find it interesting to compare the way different languages spell "else if". Let's see...

C else if
Fortran elseif, with spaces anywhere you please
Tcl elseif
Perl, Ada elsif
C preprocessor #elif
Python, bash elif

Noise Words

AMG: if has two noise words: then and else. Do any other core commands have such noise words?

I'm not entirely clear on why elseif is mandatory whereas then and else are optional. If all three words are deleted from if's little language, it would be possible to use parity to determine which arguments are conditions and which are code. The odd-numbered arguments (first, third, etc.) are conditions, the even-numbered arguments are code to run when the preceding condition is true, and if there's an odd number of total arguments, the last one is code to run when all the conditions are false. There must be at least two arguments.

if a b                      ;# if {a} then {b}
if a b c                    ;# if {a} then {b} else {c}
if a b c d                  ;# if {a} then {b} elseif {c} then {d}
if a b c d e                ;# if {a} then {b} elseif {c} then {d} else {e}

The only reason I can think of to require elseif is to help identify "parity errors" caused by an omitted or extra word. In the above example, see how "c" is a script on the second line and a conditional on the third and fourth lines? Because elseif is required, accidentally deleting the last argument won't convert the second-to-last argument from a conditional to a script, but instead will result in an error.

I'm not sure that was the rationale for requiring it, because if it was, why isn't else required too? Hysterical raisins, perhaps?

Don't get me wrong, I'm not complaining, and I'm not proposing any changes. I never omit else, so I don't mind the fact that elseif is required.

AMG: Does anyone use then? Does anyone not use else? Anyone at all?

glennj: I will, but only for "one-liners" where the body is a single word:

if {something} then return
foreach x $list {
    if {some other thing} then continue
    # ...
}

Bracing

AMG: I recommend bracing the expression arguments to if even when they're simply command substitutions, at least when using elseif and else. This preserves correct behavior even when the commands have side effects. Without bracing, all the command substitutions are done before if even executes, so they take up time and incur side effects, no matter which logical branch, if any, is executed.