1.5 Control Structures
The next example uses variables and substitutions along with some simple control structures to create a Tcl procedure called factorial, which computes the factorial of a given non-negative integer value:
proc factorial {val} { set result 1 while {$val>0} { set result [expr $result*$val] incr val -1 } return $result }
If you enter the preceding lines in wish or tclsh, or if you enter them into a file and then source the file, a new command factorial becomes available. The command takes one non-negative integer argument, and its result is the factorial of that number:
factorial 3 ⇒ 6 factorial 20 ⇒ 2432902008176640000 factorial 0.5 Ø expected integer but got "0.5"
This example uses one additional piece of Tcl syntax: braces. Braces are like double quotes in that they can be placed around a word that contains embedded spaces. However, braces are different from double quotes in two respects. First, braces nest. The last word of the proc command starts after the open brace on the first line and contains everything up to the close brace on the last line. The Tcl interpreter removes the outer braces and passes everything between them, including several nested pairs of braces, to proc as an argument. The second difference between braces and double quotes is that no substitutions occur inside braces, whereas they do inside quotes. All of the characters between the braces are passed verbatim to proc without any special processing.
The proc command takes three arguments: the name of a procedure, a list of argument names separated by whitespace, and the body of the procedure, which is a Tcl script. proc enters the procedure name into the Tcl interpreter as a new command. Whenever the command is invoked, the body of the procedure is evaluated. While the procedure body is executing, it can access its arguments as variables: val holds the first and only argument.
The body of the factorial procedure contains three Tcl commands: set, while, and return. The while command does most of the work of the procedure. It takes two arguments, an expression, $val>0, and a body, which is another Tcl script. The while command evaluates its expression argument and if the result is nonzero, it evaluates the body as a Tcl script. It repeats this process over and over until eventually the expression evaluates to zero. In the example, the body of the while command multiplies the result by val and then uses the incr command to add the specified integer increment (-1 in this case) to the value contained in val. When val reaches zero, result contains the desired factorial.
The return command causes the procedure to exit with the value of the variable result as the procedure's result. If a return command is omitted, the return value of a procedure is the result of the last command executed in the procedure's body. In the case of factorial this would be the result of while, which is always an empty string.
The use of braces in this example is crucial. The single most difficult issue in writing Tcl scripts is managing substitutions: making them happen when you want them and preventing them when you don't. The body of the procedure must be enclosed in braces because we don't want variable and command substitutions to occur at the time the body is passed to proc as an argument; we want the substitutions to occur later, when the body is evaluated as a Tcl script. The body of the while command is enclosed in braces for the same reason: rather than performing the substitutions once, while parsing the while command, we want the substitutions to be performed over and over, each time the body is evaluated. Braces are also needed in the {$val>0} argument to while. Without them the value of the variable val would be substituted when the while command is parsed; the expression would have a constant value and while would loop forever. Try replacing some of the braces in the example with double quotes to see what happens.
The examples in this book use a style in which the open brace for an argument that is a Tcl script appears at the end of one line, the script follows on successive indented lines, and the close brace is on a line by itself after the script. Although this makes for readable scripts, Tcl doesn't require this particular syntax. Arguments that are scripts are subject to the same syntax rules as any other arguments; in fact, the Tcl interpreter doesn't even know that an argument is a script at the time it parses it. One consequence is that the open brace must be on the same line as the preceding portion of the command. If the open brace is moved to a line by itself, the newline before the open brace terminates the command.
The variables in a procedure are normally local to that procedure and are not visible outside the procedure. In the factorial example the local variables include the argument val as well as the variable result. A fresh set of local variables is created for each call to a procedure (arguments are passed by copying their values), and when a procedure returns, its local variables are deleted. Variables named outside any procedure are called global variables; they last forever unless explicitly deleted. You'll find out later how a procedure can access global variables and the local variables of other active procedures. Additionally, persistent variables can be created within specific namespaces to prevent naming conflicts; Chapter 10 discusses the use of namespaces.