:TITLE: Variable scope - global and upvar ;# ;# RCSID: $Header: /cvsroot/tcl/tcltutorial/original/Tcl13.lsn,v 1.1 2004/11/04 16:01:13 davidw Exp $ ;# Copyright (c) 1995 Clif Flynt ;# 9300 Fleming Rd. ;# Dexter, MI 48130 ;# clif@cflynt.com ;# See file "NOTICE" for licensing terms. ;# :LESSON_TEXT_START_LEVEL 0: Tcl evaluates a variable name within one of two scopes: the local scope within a proc, and a global scope (the code and variables outside of any proc). Like C, Tcl defines only one global space.
The scope in which a variable will be evaluated can be changed with the
global or upvar command.
The global command will cause a variable in a local scope to be
evaluated in the global scope instead.
The upvar command behaves similarly in that it changes a
variables scope, but differently in that it can map the variable to a
new name. Upvar ties
the name of a variable in the current scope to a variable in a different
scope. This is commonly used to simulate pass-by-reference to procs.
For example, this code will link the variable y in the proc
to the variable z in the global scope:
proc p {x} {
upvar $x y
puts "$y"
}
set z "A"
p z
The syntax for upvar is:
upvar ?level? otherVar1 myVar1 ?otherVar2 myVar2 ...?
Upvar causes myVar1 to become a reference to
otherVar1, and myVar2 to become a reference to
otherVar2, etc. The otherVar variable is declared to be
at level relative to the current procedure. By default
level is 1, the next level up.
If a number is used for the level, then level references that many levels up the stack from the current level.
If the level number is preceeded by a #
symbol, then it references that many levels down from the global
scope. If level is #0, then the
reference is to a variable at the global level.
Note that since there is only one global space it is surprisingly easy to have name conflicts if you are importing other peoples code and aren't careful. It is recommended that you start global variables with an identifiable prefix to help avoid unexpected conflicts. :TEXT_END: :LESSON_TEXT_START_LEVEL 1: Tcl evaluates a variable name within one of two scopes: the local scope within a proc, and a global scope (the code and variables outside of any proc). Like C, Tcl defines only one global space.
The scope in which a variable will be evaluated can be changed with the
global or upvar command.
The global command will cause a variable in a local scope to be
evaluated in the global scope instead.
The upvar command behaves similarly. Upvar ties
the name of a variable in the current scope to a variable in a different
scope. This is commonly used to simulate pass-by-reference to procs.
The syntax for upvar is:
upvar ?level? otherVar1 myVar1 ?otherVar2 myVar2 ...?
Upvar causes myVar1 to become a reference to otherVar1,
and myVar2 to become a reference to otherVar2, etc.
The otherVar variable is declared to be at level relative to the current
procedure. By default level is 1, the next level up.
If a number is used for the level, then level references that many levels up the stack from the current level.
If the level number is preceeded by a #
symbol, then it references that many levels down from the global
scope. If level is #0, then the
reference is to a variable at the global level.
My personal opinion is that using upvar with anything except #0 or 1 is asking for trouble.
The use of global is hard to avoid, but you should avoid having too many global variables. If you start needing lots of globals, you may want to look at your design again.
Note that since there is only one global space it is surprisingly easy to have name conflicts if you are importing other peoples code and aren't careful. It is recommended that you start global variables with an identifiable prefix to help avoid unexpected conflicts.
:TEXT_END: :LESSON_TEXT_START_LEVEL 2: Tcl evaluates a variable name within one of two scopes: the local scope within a proc, and a global scope (the code and variables outside of any proc).
The local scope means that you can have variables in your proc with the
same name as variables in the calling proc (or mainline code) without
the values of one variable stepping on the value of the other.
for instance:
This code would never exit if the i in shortloop were the same as the i
in the mainline code. Every time shortloop was called, it would set
i back to 10.
proc ShortLoop {} {
for{set i 0} {$i < 10} {incr i} { ... do stuff ..}
}
...
for {set i 0} {$i < 11} {incr i} { ShortLoop }
Sometimes, though, you need access to a variable in a different scope than the current one. For instance, when you have a proc that needs to change the value of an argument you need this facility. It is also much more convenient to keep one copy of a state variable that all code can reference than to keep passing copies of that variable around.
The scope in which a variable will be evaluated can be changed with the
global or upvar command.
The global command will cause a variable in a local scope to be
evaluated in the global scope instead. In the previous example, if there
were a line
global i
in shortloop before the loop, then i would be global and the loop would never exit.
The upvar command behaves similarly. Upvar ties
the name of a variable in the current scope to a variable in a different
scope. This is commonly used to simulate pass-by-reference to procs.
The syntax for upvar is:
upvar ?level? othervar1 myvar1 ?othervar2 myvar2 ...?
Upvar causes myVar1 to become a reference to otherVar1,
and myVar2 to become a reference to otherVar2, etc.
The otherVar variable is declared to be at level relative to the current
procedure. By default level is 1, the next level up.
If a number is used for the
level, then level references that many levels up the stack from
the current level. The default, 1, references the procedure that
called this procedure, while a 2 would reference the procedure
that called the procedure that called this procedure, etc.
If the level number is preceeded by a #
symbol, then it references that many levels down from the global
scope.
If level is #0, then the reference is to a variable at the global level. In this case, a proc doesn't need to know how many levels down it has been called to reference a variable up several levels.
Another digression on programming style.
My personal opinion is that using upvar with anything except 0 or 1 is
asking for trouble. A proc with upvar 2 .. in it has too
much intimate knowlege of code that is too far away from it, and you will
get bitten someday when you try to use that proc in other code, or need
to modify the code two levels up. Even something as simple as changing a
variable name could cause working code to break.
The use of global is hard to avoid, but you should avoid having too many
global variables. If you start needing lots of globals, you may want to
look at your design again. It is recommended that you start all globals
in a package with a common prefix to separate them from similar globals
that may exist in other packages you will need to incorporate later.
Instead of a global variable name like itemCount use
TestApp_itemCount. This will help avoid unexpected name
clashes.
The other trick to avoid having too many globla variables is to use
the associative arrays. This lets you use one global variable, like
TestAppState and indices like itemCount,
dataList
The best solution is to also use namespaces, covered in future lessons.
:TEXT_END:
:CODE_START:
;# An example of Upvar
;# Convert a value to a positive number before assigning.
proc SetPositive {variable value} {
upvar $variable myvar;
if {$value < 0} { set myvar [expr -$value];} else {set myvar $value;}
return $myvar;
}
SetPositive x 5;
SetPositive y -5;
puts "X : $x Y: $y\n"
;# nesting Upvars
;# A second level proc - This will be called by one
proc two {y} {
upvar 1 $y z ;# tie the calling value to variable z
upvar 2 x a ;# Tie variable x two levels up to a
puts "two: Z: $z A: $a" ;# Output the values, just to confirm
set z 1; ;# Set z, the passed variable to 1;
set a 2; ;# Set x, two layers up to 2;
}
;# A first level proc - This will be called by the global space code.
proc one {y} {
upvar $y z ;# This ties the calling value to variable z
puts "one: Z: $z" ;# Output that value, to check it is 5
two z; ;# call proc two, which will change the value
}
one y; ;# Call one, and output X and Y after the call.
puts "\nX: $x Y: $y"
:TEXT_END: