Practical Introduction to Upvar and Uplevel

marsd wrote this and he's not proud.

I had a lot of trouble with upvar and uplevel up till a year or so ago so I figured I'd give a short overview of what I've learned of dealing with them.

I'll just use two basic proc's for this "tutorial":

Number One:(upvar)

proc pass {val {bdy "puts $LOCAL"} {lvl "#0"}} {
    upvar $lvl $val LOCAL
    eval $bdy

Description: A flexible proc that accepts various arguments:

val = the upvar variable to work with.

bdy = the commands to be executed..default is just to print to stdout the pointed variable.

lvl = what stacklevel do we want to work with?

Number two:(uplevel)

proc putUp {cmd {lvl "#0"}} {
    uplevel $lvl "eval $cmd"

Description: A flexible proc that evaluates $cmd at the lvl pointed stack. Disclaimer: I've got some negative feedback for using uplevel this way. Using uplevel this way WILL affect your program differently than using uplevel {command}. The method "cmd" allows transparent use of local and global variables, the other {cmd} causes problems with local variable evaluation. Maybe an expert could discuss the evaluation issue.

escargo 22 Sep 2005 - Is this "eval" necessary?

Lars H: No, it will only lead to some quoting hell.

Notes on levels: The "lvl" default arg in both proc's refers to a "stack level", a reference to #0 means global, #1 is up one level, #2 up two. There is no converse. No downlevel. The examples given will work well enough for all stack levels.

Lars H: That's a bit confusingly written, really. #1 is the level called from the global level. #2 is the level called from level #1, etc., so in increasing these numbers you in a sense go down (i.e., not as high up). In order to go up from the current level use numbers without #:

uplevel 0 $cmd;   # Equivalent to [eval $cmd].
uplevel 1 $cmd;   # Evaluate $cmd in your caller, equivalent to [uplevel $cmd].
uplevel 2 $cmd;   # Evaluate $cmd in your caller's caller. Equivalent to [uplevel [list uplevel $cmd]].

There's of course no way to go "down" from the current level, because there's nothing there; you're always conceptually at the end of a stack growing downwards.

Just Jump in here: Try running this example in tkcon or tclsh after loading the above procs into them.

set x 12 

pass x 

putUp {puts $x}  

Well if you've worked with upvar and uplevel before you'll know that's what these commands do, and should probably be somewhere else ;)

You should get 12 for each.

Now we modify the values twice via both proc's:

pass x {set LOCAL 3 ; puts $LOCAL ; putUp {set x 12}}

puts $x

putUp {set x 3 ; puts $x ; pass x {set LOCAL 12}}

puts $x

Break the command down and try it piece by piece if you don't follow the above.

This one is simpler, unsetting x:

pass x {unset LOCAL}

set x 12

putUp {unset x}

This one may be a little confusing:

putUp {proc foo {{x} {puts $x ; pass x {set LOCAL 21} "#1"}}}

rename foo ""

This calls the pass proc from a "dynamically" created proc named foo, created at the global stack level by the putUp proc.

The pass proc resets a variable passed to foo from global space and resets it at stack level 1, inside foo.

Control passes back and we destroy foo. A one shot wonder.

This is the real power of tcl illustrated. The flexibility of the language is amazing.


Be aware that the info commands are very helpful in dynamically creating and keeping straight your processes called at various levels.

set x 12

pass x {puts "[[info level]], [[info level 1]]" ; set LOCAL 2}

The command {info level} tells what stack level the calling process is coming from. With the additional {info level "level"} arg the interpreter looks for any operations at that stack level.

Happy Tcling!

Good God this formatting is a bear!!!!