Variable aliases

Andreas Drollinger 2012-12-09 - This page shows how creating variable aliases in Tcl.

While Tcl provides the command interp alias to create command aliases, Tcl provides at first glance no mechanism to create variable aliases. However, the following code examples show that the Tcl command upvar cannot refer only variables of a different stack frame (as this is explained by the Tcl documentation), but upvar can also refer variables inside the same stack frame. This can be considered as a variable alias.

To remember, upvar's syntax is:

upvar ?level? otherVar myVar ?otherVar myVar ...?

The following example creates a variable v which is then referred by the variable a. To refer v inside the current stack frame, the upvar's stack frame level argument has to be set to 0:

set v 123
upvar 0 v a

puts $a
==>123

Referring a variable inside the current namespace via upvar works also perfectly inside a procedure:

proc p {} {
   set v 123
   upvar 0 v a
   puts $a
}

p
==>123

This allows defining for convenience an procedure to create variable aliases:

proc valias {v_orig v_alias} {
   uplevel 1 "upvar 0 $v_orig $v_alias"
}

set v 123
valias v a
puts $a
==>123

Such a variable alias allows changing the referenced variable and to read the updated value via the alias variable:

# ... continuation from previous code snippet

set v 987
puts $a
==>987

But, also the alias variable can be changed, and the updated value can be read via the referenced variable:

# ... continuation from previous code snippet

set a 876
puts $v
==>876

It's obvious that the original referenced variable cannot be accessed anymore if it is deleted. But the alias variable is also deleted at the same time:

# ... continuation from previous code snippet

unset v
puts $v
==>can't read "v": no such variable
puts $a
==>can't read "a": no such variable

And vice versa: If the alias variable is deleted, also the referenced variable will be deleted:

set v 123
upvar 0 v a

unset a
puts $a
==>can't read "a": no such variable
puts $v
==>can't read "v": no such variable

It is interesting that Tcl remembers the link between the referenced variable and the variable alias. If the referenced variable is restored, also the alias variable is restored automatically.

# ... continuation from previous code snippet

set v 987
puts $a
987

And if the alias variable is restored, also the referenced variable is again available:

# ... continuation from previous code snippet

unset v
puts $v
==>can't read "v": no such variable
puts $a
==>can't read "a": no such variable

set a 159
puts $v
159

Arrays

Upvar allows referring entire arrays, as the following example shows. The array variable va is created and accessed then via the alias variable aa:

array set va {a 123 b 234 c 345}
upvar #0 va aa
array get aa
==>a 123 b 234 c 345

An update of the alias variable aa will immediately update also the referenced variable:

# ... continuation from previous code snippet

set aa(d) 456
array get va
==>d 456 a 123 b 234 c 345

Namespaces

Upvar allows also creating alias variables that refer variables inside namespaces. The following code snipped defines the two namespaces n1 and n2 that contain each one the variable v:

namespace eval n1 {variable v n1_123}
namespace eval n2 {variable v n2_123}

The following lines create the alias variable a that refers the variable v of the first namespace:

# ... continuation from previous code snippet

upvar 0 n1::v a
puts $a
==>n1_123

The reference can be changed to the variable v of the second namespace by running another upvar command:

# ... continuation from previous code snippet

upvar 0 n2::v a
puts $a
==>n2_123

Deleting the namespace of the currently referenced variable, the alias variable will also be deleted:

# ... continuation from previous code snippet

namespace delete n2
puts $a
==>can't read "a": no such variable

However, in opposite to referenced variables that are deleted, Tcl keeps not anymore the reference information if the referenced variable’s namespace is deleted. After restoring the deleted namespace with its variable, the alias variable is still not anymore available:

# ... continuation from previous code snippet
namespace eval n2 {variable v n2_123}
puts $a
==>can't read "a": no such variable

Procedures and alias variables declared as global

Be careful if you use procedures that define an alias variable that is declared at the same time as a global variable. The following example defines two namespaces that contain each the variable v. A procedure select_namespace allows selecting one of the namespace and assigning the variable v of the selected namespace to the global variable a:

namespace eval n1 {variable v n1_123}
namespace eval n2 {variable v n2_123}

proc select_namespace {ns} {
   global a
   upvar #0 ${ns}::v ::a
   puts "$a - $::a"
}

If the first namespace is selected, select_namespace displays as this is expected the value of the variable of the first namespace. However, if the second namespace is selected, select_namespace still prints the variable of the first namespace!

# ... continuation from previous code snippet

select_namespace n1
==>n1_123 - n1_123
select_namespace n2
==>n1_123 - n2_123

What is happening? It seems that the Tcl interpreter maps the global variable into the procedure stack level at the moment the global variable is declared. In this case this variable refers to the variable n1::v. Changing the reference to a variable of another namespace is not updating this mapping of the declared variable.

To make the reference of a global variable to a variable in a certain namespace inside the context of a procedure working, the reference to the global variable needs to be made first via upvar, using an explicate global namespace prefix ::, and the global variable needs then to be declared as global in a second step:

# ... continuation from previous code snippet

namespace eval n1 {variable v n1_123}
namespace eval n2 {variable v n2_123}

proc select_namespace {ns} {
   upvar #0 ${ns}::v ::a
   global a
   puts "$a - $::a"
}

select_namespace n1
==>n1_123 - n1_123
select_namespace n2
==>n2_123 - n2_123

It is of course also possible to access anyway the global variable always with the global namespace prefix :: inside the procedure:

# ... continuation from previous code snippet

proc select_namespace {ns} {
   upvar #0 ${ns}::v ::a
   puts $::a
}

select_namespace n1
==>n1_123
select_namespace n2
==>n2_123

Application

Variable references can be used to extend an application to support multiple contexts, without the need to change the entire application to handle the different datasets of the contexts. The dataset of each context can for example be stored for this inside a dedicated namespace. In the following example there are two of these namespaces, each one contains an array variable that has 1'000'000 elements:

foreach dsn {0 1} {
   namespace eval dataset($dsn) {
      for {set k 0} {$k<1000000} {incr k} {
         set v($k) [expr {rand()}]
      }
   }
}

A brute force solution to make the dataset's variable available for the application inside the global namespace would be to make simply a copy. However, copying huge amount of data is time consuming (and of course also memory consuming):

# ... continuation from previous code snippet

proc select_dataset {dsn} {
   array set ::v [array get ::dataset($dsn)::v]
}

time {select_dataset 0}
==>533581 microseconds per iteration
time {select_dataset 1}
==>485014 microseconds per iteration

A much more elegant solution is to use variable references that can be created nearly instantaneously:

# ... continuation from previous code snippet

unset v; # Clear the previously crated variable inside the global namespace
proc select_dataset {dsn} {
   upvar #0 dataset($dsn)::v ::v
}

time {select_dataset 0}
==>14 microseconds per iteration
time {select_dataset 1}
==>14 microseconds per iteration

See also: