:TITLE: Variable scope - global and upvar
;#
;# RCSID: $Header: /home/cvs/TclTutor3/lesson/Tcl_English/Tcl_English13.lsn,v 1.1 2008/08/02 12:37:17 clif 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.
<P>
The scope in which a variable will be evaluated can be changed with the
<CODE>global</CODE> or <CODE>upvar</CODE> command.
<P>
The <CODE>global</CODE> command will cause a variable in a local scope to be 
evaluated in the global scope instead.
<P>
The <CODE>upvar</CODE> command behaves similarly in that it changes a
variables scope, but differently in that it can map the variable to a
new name.  <CODE>Upvar</CODE> 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.
<P>
For example, this code will link the variable <CODE>y</CODE> in the proc
to the variable <CODE>z</CODE> in the global scope:
<CODE><PRE>
proc p {x} {
  upvar $x y
  puts "$y"
  }
set z "A"
p z
</PRE></CODE>
<P>
The syntax for <CODE>upvar</CODE> is:
<P><B>upvar</B> <I> ?level? otherVar1 myVar1 ?otherVar2 myVar2 ...? </I>
<P>
<CODE>Upvar</CODE> causes <I>myVar1</I> to become a reference to
<I>otherVar1</I>, and <I>myVar2</I> to become a reference to
<I>otherVar2</I>, etc. The <I>otherVar</I> variable is declared to be
at <I>level</I> relative to the current procedure.  By default
<I>level</I> is 1, the next level up.
<P>  If a number is used for the
<I>level</I>, then level references that many levels up the stack from
the current level.  
<P> If the <CODE>level</CODE> number is preceeded by a <CODE>#</CODE>
symbol, then it references that many levels down from the global
scope.   If <I>level</I> is <CODE>#0</CODE>, then the
reference is to a variable at the global level.
<P>
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.
<P>
The scope in which a variable will be evaluated can be changed with the
<CODE>global</CODE> or <CODE>upvar</CODE> command.
<P>
The <CODE>global</CODE> command will cause a variable in a local scope to be 
evaluated in the global scope instead.
<P>
The <CODE>upvar</CODE> command behaves similarly.  <CODE>Upvar</CODE> 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.
<P>
The syntax for <CODE>upvar</CODE> is:
<P><B>upvar</B> <I> ?level? otherVar1 myVar1 ?otherVar2 myVar2 ...? </I>
<P>
<CODE>Upvar</CODE> causes <I>myVar1</I> to become a reference to <I>otherVar1</I>,
and <I>myVar2</I> to become a reference to <I>otherVar2</I>, etc.
The <I>otherVar</I> variable is declared to be at <I>level</I> relative to the current
procedure.  By default <I>level</I> is 1, the next level up.  
<P>  If a number is used for the
<I>level</I>, then level references that many levels up the stack from
the current level.  
<P> If the <CODE>level</CODE> number is preceeded by a <CODE>#</CODE>
symbol, then it references that many levels down from the global
scope.   If <I>level</I> is <CODE>#0</CODE>, then the
reference is to a variable at the global level.
<P>
<P>
My personal opinion is that using upvar with anything except #0 or 1 is
asking for trouble.
<P>
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.
<P>
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.
<P>
: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).
<P>
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:
<CODE><PRE>
proc ShortLoop {} {
  for{set i 0} {$i &lt; 10} {incr i} { ... do stuff ..}
  }

...
for {set i 0} {$i &lt;  11} {incr i} { ShortLoop }
</PRE></CODE>
This code would never exit if the <B>i</B> in <B>shortloop</B> were the same as the <B>i</B>
in the mainline code.  Every time <B>shortloop</B> was called, it would set
<B>i</B> back to 10.
<P>
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.
<P>
The scope in which a variable will be evaluated can be changed with the
<CODE>global</CODE> or <CODE>upvar</CODE> command.
<P>
The <CODE>global</CODE> 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 <P>
<CODE>global i</CODE><P>
in shortloop before the loop, then <B>i</B> would be global and the loop
would never exit.
<P>
The <CODE>upvar</CODE> command behaves similarly.  <CODE>Upvar</CODE> 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.
<P>
The syntax for <CODE>upvar</CODE> is:
<P><B>upvar</B> <i> ?level? othervar1 myvar1 ?othervar2 myvar2 ...? </i>
<P>
<CODE>Upvar</CODE> causes <I>myVar1</I> to become a reference to <I>otherVar1</I>,
and <I>myVar2</I> to become a reference to <I>otherVar2</I>, etc.
The <I>otherVar</I> variable is declared to be at <I>level</I> relative to the current
procedure.  By default <I>level</I> is 1, the next level up.  
<P>
<P>  If a number is used for the
<I>level</I>, 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 <CODE>2</CODE> would reference the procedure
that called the procedure that called this procedure, etc.

<P> If the <CODE>level</CODE> number is preceeded by a <CODE>#</CODE>
symbol, then it references that many levels down from the global
scope.   
<P>
If <I>level</I>
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.
<P>
Another digression on programming style.
<P>
My personal opinion is that using upvar with anything except 0 or 1 is
asking for trouble.  A proc with <CODE>upvar 2 ..</CODE> 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.
<P>
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 <CODE>itemCount</CODE> use
<CODE>TestApp_itemCount</CODE>.  This will help avoid unexpected name
clashes.
<P>
The other trick to avoid having too many globla variables is to use
the associative arrays.  This lets you use one global variable, like
<CODE>TestAppState</CODE> and indices like <CODE>itemCount</CODE>,
<CODE>dataList</CODE, etc.
<P>
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:

