Question

I have a question about upvar command in TCL. Using upvar command, we have have a reference to a global variable or a local variable in other procedure. I saw the following code:

proc tamp {name1 name2} {
    upvar $name1 Ronalod
    upvar $name2 Dom 
    set $Dom "Dom"
}

this procedure is called as tamp name1 name2 , and there is no global variables name1, name2 defined outside of it, how this upvar works in this case?

Was it helpful?

Solution

When you call upvar 1 $foo bar, it finds the variable in the caller's scope whose name is in the foo variable, and makes the local bar variable into an alias for it. If the variable doesn't exist, it is created in an unset state (i.e., the variable record exists but it has no value. In fact, the implementation uses a NULL to represent that information, which is why Tcl has no NULL equivalent; NULL indicates non-existence) but the link is still created. (It only gets torn down when the local scope is destroyed or if upvar is used to point the local variable at something else.)

So let's look at what your code is really doing:

proc tamp {name1 name2} {
    upvar $name1 Ronalod
    upvar $name2 Dom 
    set $Dom "Dom"
}

The first line says that we're creating a command called tamp as a procedure, that that procedure will have two mandatory formal arguments, and that those arguments are called name1 and name2.

The second line says that we're binding a variable name in the caller (the 1 level indicator from my earlier explanation is optional, but strongly advised in idiomatic code) that is given by the name1 variable (i.e., the first argument to the procedure) to the local variable Ronalod. Henceforth, all accesses to that local variable (until the end of the stack frame's life) will be actually performed on the bound variable in the caller.

The third line is pretty much the same, except with name2 (second argument) and Dom (local variable).

The fourth line is actually pretty funky. It reads the Dom variable to get a variable name (i.e., the variable named in the second argument to the procedure call) and sets that named variable to the value Dom. Remember, in Tcl you use $ to read from a variable, not to talk about the variable.

The result of the procedure call will be the result of the last command in its body (i.e., the literal Dom since set yields the contents of the variable as its result, a value it has just assigned). (The last line is wholly uninteresting as it is just the end of the procedure body.)

The net result of calling this command is actually going to be pretty much nothing at all unless the second argument names a variable containing either Ronalod or Dom. That's pretty confusing. Of course, the confusing bit is really that funky set with a variable first argument. (That's almost always a bad idea; it's an indicator of Bad Code Smell.) Had you used this instead, things would have been simpler:

set Dom "Dom"

In this case, the variable that Dom is coupled to (i.e., the one named by the second argument to the procedure) would be set to Dom; the variables would in effect be being passed-by-reference. That extra $ makes a huge difference!

OTHER TIPS

name1 and name2 don't exist in the calling scope - they are simply parameters to your proc. For example you might call the proc as follows:

% set first "James"
James
% set last "Bond"
Bond
% tamp first last
Dom

As it stands your proc really doesn't do anything. If you modify the last line as follows, then it makes more sense:

proc tamp {name1 name2} {
    upvar $name1 Ronalod
    upvar $name2 Dom 
    set Dom "Dom"
}
% tamp first last
Dom
% puts $first
James
% puts $last
Dom

One of the best guides I've seen to use of upvar and uplevel is Rob Mayoff's guide http://www.dqd.com/~mayoff/notes/tcl/upvar.html


I'm adding a further example to help you to see that name1 and name2 are just input parameters and don't need to exist globally. Run this example in tclsh and see if it makes more sense.

% proc tamp {name1 name2} {
    upvar $name1 Ronalod
    upvar $name2 Dom
    puts "Ronalod=$Ronalod, Dom=$Dom"
    set Ronalod "Brian"
    set Dom "Fenton"
    puts "NOW: Ronalod=$Ronalod, Dom=$Dom"
}
%
% tamp name1 name2
can't read "Ronalod": no such variable
% set first "James"
James
% set last "Bond"
Bond
% tamp first last
Ronalod=James, Dom=Bond
NOW: Ronalod=Brian, Dom=Fenton
% puts $first
Brian
% puts $last
Fenton

When the Tcl intepreter enters a procedure written in Tcl, it creates a special table of variables local to that procedure while its code is being executed. This table can contain both "real" local variables and special "links" to other variables. Such links are indistinguishable from "real" variables as long as Tcl commands (such as set, unset etc) are concerned.

These links are created by the upvar command, and it's able to create a link to any variable on any stack frame (including the global scope—frame 0).

Since Tcl is highly dynamic, its variables can come and go any time and so a variable linked to by upvar might not exist at the time a link to it is created, observe:

% unset foo
can't unset "foo": no such variable
% proc test name { upvar 1 $name v; set v bar }
% test foo
bar
% set foo
bar

Note that I first demonstrate that the variable named "foo" does not exist, then set it in a procedure which uses upvar (and the variable is autocreated) and then demonstrate that the variable exists after the procedure has quit.

Also note that upvar is not about accessing global variables—this is usually achieved using the global and variable commands; instead, upvar is used to work with variables rather than values. This is usually needed when we need to change something "in place"; one of the better examples of this is the lappend command which accepts the name of a variable containing a list and appends one or more elements to that list, changing it in place. To achieve this, we pass lappend the name of a variable, not just a list value itself. Now compare this with the linsert command which accepts a value, not a variable, so it takes a list and produces another list.

Another thing to note is that by default (in its two-argument form), upvar links to a variable with the specified name one level up the stack, not to a global variable. I mean, you can do this:

proc foo {name value} {
  upvar $name v
  set v $value
}

proc bar {} {
  set x ""
  foo x test
  puts $x ;# will print "test"

}

In this example, the procedure "foo" changes the variable local to procedure "bar".

Hence, to make the intent more clear, many people prefer to always specify the number of stack frames upvar should "climb up", like in upvar 1 $varName v which is the same as upvar $varName v but clearer.

Another useful application of this is referring to local variables, by specifying zero stack levels to climb up—this trick is sometimes useful to more conveniently access variables in arrays:

proc foo {} {
   set a(some_long_name) test
   upvar 0 a(some_long_name) v
   puts $v ;# prints "test"
   upvar a(some_even_more_long_name) x
   if {![info exists x]} {
     set x bar
   }
}

As a bonus, note that upvar also understands absolute numbers of stack frames which are specified using the "#" prefix, and "#0" means the global scope. That way you could bind to a global variable while the procedure in your original example only would bind to global variables if executed in the global scope.

Well the upvar in tcl, is using to define vars to will be use after the proc is executed. Example:

proc tamp {name1 name2} {
    upvar 1 $name1 Ronalod
    upvar 1 $name2 Dom 
    set $Dom "Dom"
}

#Call the proc tamp

tamp $name1 $name2

#Now we can use the vars Ronalod and Dom

set all_string "${Ronalod}${Dom}"

Now the number 1 in the follow command

upvar 1 $name2 Dom 

Indicate the level in that you can use the vars, for example if we use 2

upvar 2 $name2 Dom

so I can do this:

proc tamp_two {name1 name2} {
      # do something ...
      tamp $name1 $name2
}

proc tamp {name1 name2} {
    upvar 2 $name1 Ronalod
    upvar 2 $name2 Dom 
    set $Dom "Dom"
}

#Call the proc tamp_two

tamp_two $name1 $name2

#Now we can use the vars Ronalod and Dom

set all_string "${Ronalod}${Dom}"

this can be posible with the upvar 2, if we keep upvar 1, it doesn't work.

I hope this will be usefull to you.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top