Chapter 7. Procedures and Scope

Procedures encapsulate a set of commands, and they introduce a local scope for variables. Commands described are: proc, global, and upvar.

Procedures parameterize a commonly used sequence of commands. In addition, each procedure has a new local scope for variables. The scope of a variable is the range of commands over which it is defined. Originally, Tcl had one global scope for shared variables, local scopes within procedures, and one global scope for procedures. Tcl 8.0 added namespaces that provide new scopes for procedures and global variables. For simple applications you can ignore namespaces and just use the global scope. Namespaces are described in Chapter 14.

The proc Command

A Tcl procedure is defined with the proc command. It takes three arguments:

proc name params body

The first argument is the procedure name, which is added to the set of commands understood by the Tcl interpreter. The name is case sensitive and can contain any characters. Procedure names do not conflict with variable names. The second argument is a list of parameter names. The last argument is the body of the procedure.

Once defined, a Tcl procedure is used just like any other Tcl command. When it is called, each argument is assigned to the corresponding parameter and the body is evaluated. The result of the procedure is the result returned by the last command in the body. The return command can be used to return a specific value.

Procedures can have default parameters so that the caller can leave out some of the command arguments. A default parameter is specified with its name and default value, as shown in the next example:

Example 7-1. Default parameter values

proc P2 {a {b 7} {c -2} } {
   expr $a / $b + $c
}
P2 6 3
=> 0

Here the procedure P2 can be called with one, two, or three arguments. If it is called with only one argument, then the parameters b and c take on the values specified in the proc command. If two arguments are provided, then only c gets the default value, and the arguments are assigned to a and b. At least one argument and no more than three arguments can be passed to P2.

A procedure can take a variable number of arguments by specifying the args keyword as the last parameter. When the procedure is called, the args parameter is a list that contains all the remaining values:

Example 7-2. Variable number of arguments

proc ArgTest {a {b foo} args} {
   foreach param {a b args} {
      puts stdout "	$param = [set $param]"
   }
}
set x one
set y {two things}
set z [special$
ArgTest $x
=> a = one
   b = foo
   args =
ArgTest $y $z
=> a = two things
   b = [special$
   args =
ArgTest $x $y $z
=> a = one
   b = two things
   args = {[special$}
ArgTest $z $y $z $x
=> a = [special$
   b = two things
   args = {[special$} one

The effect of the list structure in args is illustrated by the treatment of variable z in Example 7-2. The value of z has special characters in it. When $z is passed as the value of parameter b, its value comes through to the procedure unchanged. When $z is part of the optional parameters, quoting is automatically added to create a valid Tcl list as the value of args. Example 10-3 on page 136 illustrates a technique that uses eval to undo the effect of the added list structure.

Changing Command Names with rename

The rename command changes the name of a command. There are two main uses for rename. The first is to augment an existing procedure. Before you redefine it with proc, rename the existing command:

rename foo foo.orig

From within the new implementation of foo you can invoke the original command as foo.orig. Existing users of foo will transparently use the new version.

The other thing you can do with rename is completely remove a command by renaming it to the empty string. For example, you might not want users to execute UNIX programs, so you could disable exec with the following command:

rename exec {}

Command renaming and deletion can be traced with the trace command described in Chapter 13.

Scope

By default there is a single, global scope for procedure names. This means that you can use a procedure anywhere in your script. Variables defined outside any procedure are global variables. However, as described below, global variables are not automatically visible inside procedures. There is a different namespace for variables and procedures, so you could have a procedure and a global variable with the same name without conflict. You can use the namespace facility described in Chapter 7 to manage procedures and global variables.

Each procedure has a local scope for variables. That is, variables introduced in the procedure live only for the duration of the procedure call. After the procedure returns, those variables are undefined. Variables defined outside the procedure are not visible to a procedure unless the upvar or global scope commands are used. You can also use qualified names to name variables in a namespace scope. The global and upvar commands are described later in this chapter. Qualified names are described on page 208. If the same variable name exists in an outer scope, it is unaffected by the use of that variable name inside a procedure.

In Example 7-3, the variable a in the global scope is different from the parameter a to P1. Similarly, the global variable b is different from the variable b inside P1:

Example 7-3. Variable scope and Tcl procedures

set a 5
set b -8
proc P1 {a} {
   set b 42
   if {$a < 0} {
      return $b
   } else {
      return $a
   }
}
P1 $b
=> 42
P1 [expr {$a*2}]
=> 10

The global Command

Global scope is the toplevel scope. This scope is outside of any procedure. Variables defined at the global scope must be made accessible to the commands inside a procedure by using the global command. The syntax for global is:

global varName1 varName2 ...

Note

The global Command

The global command goes inside a procedure.

The global command adds a global variable to the current scope. A common mistake is to have a single global command and expect that to apply to all procedures. However, a global command in the global scope has no effect. Instead, you must put a global command in all procedures that access the global variable. The variable can be undefined at the time the global command is used. When the variable is defined, it becomes visible in the global scope.

Example 7-4 shows a random number generator. Before we look at the example, let me point out that the best way to get random numbers in Tcl is to use the rand() math function:

expr rand()
=> .137287362934

The point of the example is to show a state variable, the seed, that has to persist between calls to random, so it is kept in a global variable. The choice of randomSeed as the name of the global variable associates it with the random number generator. It is important to pick names of global variables carefully to avoid conflict with other parts of your program. For comparison, Example 14-1 on page 206 uses namespaces to hide the state variable:

Example 7-4. A random number generator.[*]

proc RandomInit { seed } {
   global randomSeed
   set randomSeed $seed
}
proc Random {} {
   global randomSeed
   set randomSeed [expr ($randomSeed*9301 + 49297) % 233280]
   return [expr $randomSeed/double(233280)]
}
proc RandomRange { range } {
   expr int([Random]*$range)
}
RandomInit [pid]
=> 5049
Random
=> 0.517686899863
Random
=> 0.217176783265
RandomRange 100
=> 17

Call by Name Using upvar

Use the upvar command when you need to pass the name of a variable, as opposed to its value, into a procedure. The upvar command associates a local variable with a variable in a scope up the Tcl call stack. The syntax of the upvar command is:

upvar ?level? varName localvar

The level argument is optional, and it defaults to 1, which means one level up the Tcl call stack. You can specify some other number of frames to go up, or you can specify an absolute frame number with a #number syntax. Level #0 is the global scope, so the global foo command is equivalent to:

upvar #0 foo foo

The variable in the uplevel stack frame can be either a scalar variable, an array element, or an array name. In the first two cases, the local variable is treated like a scalar variable. In the case of an array name, then the local variable is treated like an array. The use of upvar and arrays is discussed further in Chapter 8 on page 99. The following procedure uses upvar to print the value of a variable given its name.

Example 7-5. Print variable by name

proc PrintByName { varName } {
   upvar 1 $varName var
   puts stdout "$varName = $var"
}

You can use upvar to fix the incr command. One drawback of the built-in incr is that it raises an error if the variable does not exist. We can define a new version of incr that initializes the variable if it does not already exist:

Example 7-6. Improved incr procedure

proc incr { varName {amount 1}} {
   upvar 1 $varName var
   if {[info exists var]} {
      set var [expr $var + $amount]
   } else {
      set var $amount
   }
   return $var
}

Variable Aliases with upvar

The upvar command is useful in any situation where you have the name of a variable stored in another variable. In Example 7-2 on page 88, the loop variable param holds the names of other variables. Their value is obtained with this construct:

puts stdout "	$param = [set $param]"

Another way to do this is to use upvar. It eliminates the need to use awkward constructs like [set $param]. If the variable is in the same scope, use zero as the scope number with upvar. The following is equivalent:

upvar 0 $param x
puts stdout "	$param = $x"

Associating State with Data

Suppose you have a program that maintains state about a set of objects like files, URLs, or people. You can use the name of these objects as the name of a variable that keeps state about the object. The upvar command makes this more convenient:

upvar #0 $name state

Using the name directly like this is somewhat risky. If there were an object named x, then this trick might conflict with an unrelated variable named x elsewhere in your program. You can modify the name to make this trick more robust:

upvar #0 state$name state

Your code can pass name around as a handle on an object, then use upvar to get access to the data associated with the object. Your code is just written to use the state variable, which is an alias to the state variable for the current object. This technique is illustrated in Example 17-7 on page 245.

Namespaces and upvar

You can use upvar to create aliases for namespace variables, too. Namespaces are described in Chapter 14. For example, as an alternative to reserving all global variables beginning with state, you can use a namespace to hide these variables:

upvar #0 state::$name state

Now state is an alias to the namespace variable. This upvar trick works from inside any namespace.

Commands That Take Variable Names

Several Tcl commands involve variable names. For example, the Tk widgets can be associated with a global Tcl variable. The vwait and tkwait commands also take variable names as arguments.

Note

Commands That Take Variable Namescommandpassing variable names

Upvar aliases do not work with Tk widget text variables.

The aliases created with upvar do not work with these commands, nor do they work if you use trace, which is described on page 193. Instead, you must use the actual name of the global variable. To continue the above example where state is an alias, you cannot:

vwait state(foo)
button .b -textvariable state(foo)

Instead, you must

vwait state$name(foo)
button .b -textvariable state$name(foo)

The backslash turns off the array reference so Tcl does not try to access name as an array. You do not need to worry about special characters in $name, except parentheses. Once the name has been passed into the Tk widget it will be used directly as a variable name. Text variables for labels are explained on page 490, and text variables for entry widgets are illustrated in Example 34-1 on page 508.



[*] Adapted from Exploring Expect by Don Libes, O'Reilly & Associates, Inc., 1995, and from Numerical Recipes in C by Press et al., Cambridge University Press, 1988.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.27.202