Version 50 of upvar

Updated 2014-08-19 03:32:43 by pooryorick

upvar , a built-in Tcl command, makes a variable at the current Commands%|%built-in] Tcl command, links a variable to some other variable.

See Also

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

Documentation

official reference

Description

upvar makes myVar an alias for otherVar. otherVar can upvar links otherVar to myVar, after which they are effectively the same variable, and performing an operation, e.g. set or unset, on one is equivalent to performing the operation on the other. otherVar can refer to a local variable in the current stack frame or some enclosing stack frame, or it can refer to a namespace variable. myVar is resolved relative to the current stack frame or, outside of a procedure, the current namespace. otherVar is resolved the same way, but relative to the stack frame indicated by level rather than the current stack frame. It is an error for myVar and otherVar'' to resolve to the same name unless defaults to 1.

myVar must not exist at the time when upvar is invoked. The variable named by myVar must not exist at the when upvar is invoked. The variable named by

upvar never interprets myVar as an array element. Even if the myVar looks like an array element, e.g. a(b), a regular variable is created. otherVar may refer to a scalar variable, an array, or an array element.

upvar makes it more convenient to work with variables at other levels of evaluation, and facilitates Tcl procedures that provide [new control upvar returns an empty string.

upvar makes it easier to refer to a variable by name, and also makes it easier to implement Tcl procedures that are new control [variable overlap in functionality. namespace upvar doesn't add any functionality over upvar, but its syntax, along with the accompanying difference in variable resolution makes, it more convenient in most cases for linking namespace variables. variable overlap in functionality, but have different design goals. namespace upvar doesn't add any functionality over upvar, but its syntax, along with the accompanying difference in variable resolution makes, it more convenient in most cases for linking namespace variables. A script should not usually use the literal names of variables of A script usually should not be defined using the literal names of variables of such a situation.

upvar creates myVar, but does not create otherVar:

namespace eval n2 {
** Setting a Variable in the Caller **
n1::p1

    upvar 1 $varname var
    set var 5
}
someproc a
set a ;# -> 5

Link to a global variable

upvar #0 foo foo  ;# equivalent to: global foo

MS notes that the equivalence to global is only within proc bodies: global is a no-op outside of them, upvar #0 is not.

Link to another variable in the same execution frame

upvar 0 foo bar  ;# assigns alias 'bar' to a local variable 'foo'

otherVar can be unqualified, semi-qualified, or fully qualified. In all cases, the variable is resolved at the indicated level according to normal rules. One trick is to use upvar 0 to link to another variable in the same call frame, or to a variable in some namespace:

upvar 0 ::path::to::variable myvarname

Using upvar with an array

# print elements of an array
proc show_array arrayName {
    upvar $arrayName myArray

    foreach element [array names myArray] {
       puts stdout "${arrayName}($element) = $myArray($element)"
    }
}

proc main {} {
    set arval(0) zero
    set arval(1) one
    show_array arval
}

In the example above, any changes made to elements in $myArray will be reflected in $arval.

upvar prefix* for Variables in an Array

upvar prefix* Array Elements

Art M.:

# Purpose - Utility to turn an subset of an array into alias upvar'd variables
# for local function use.
#  This assumes the array is defined in one level above where this routine is
#  called.
#  Todo -
#   allow array to be defined at other levels.
#   why does it not work unless I quote ${arName}(ind) with {} ??
#
# Right now the source array has to be in the global level
#
proc upAr {arName index} {
    upvar $arName ar
    foreach ind [array names ar ${index}*] {
        regsub $index $ind {} indVar
        if {[info level] == 1} {
            # This was executed from level 0  
            uplevel 1 [list upvar #0 ${arName}($ind) $indVar]
        } else {
            uplevel 1 [list upvar #0 ${arName}($ind) $indVar]  
        }
    }
}

Detecting a variable created by upvar

aspect 2014-08-08, on the trace page, illustrated one difference between variables created by upvar and other variables:

% unset -noc x
% set y 1; set z 1;
% upvar 0 y x
% upvar 0 z x  ;# reupvar - no problems
% upvar 0 x y
# ERROR: variable y already exists

PYK 2014-08-18: Drawing from the previous example, here is a non-destructive PYK 2014-08-18: I was just looking for a way to distinguish upvared variables. One thing I still see no way to accomplish is determining the target of an upvar'ed variable. An improvement to upvar might be to have it return the target of an upvar'ed variable when it is given only one argument. I'll come back to this page soon, delete old conversation, and move this example to upvar, unless someone else beats me to it. aspect: one (very cumbersome) way is to use traces:

Gotcha: otherVar That Looks Like a Number

PYK 2017-02-09: Unless one is interested in the history, this section is safe to AMG: I like that level is optional, but I don't like the method used to

proc naive_iplsort {lst} {
    upvar $lst var
    set var [lsort $var]
}

breaks whenever $lst starts with # or a numeral, because upvar guesses that its first parameter is level rather than otherVar.

% set data {4 3 2 1 0}; set #0 {9 8 7 6 5}; set 0a {e d c b a}
% naive_iplsort data
0 1 2 3 4
% naive_iplsort #0
wrong # args: should be "upvar ?level? otherVar localVar ?otherVar localVar ...?"
% naive_iplsort 0a
expected integer but got "0a"

The fix is to explicitly supply level:

proc paranoid_iplsort {lst} {
    upvar 1 $lst var
    set var [lsort $var]
}

This makes it abundantly clear to Tcl that $lst is an not level but an otherVar argument, but I think it's kind of painful to type, since it admits that Tcl has the feature that level is optional, but that feature can never be used safely. At least it works:

% set data {4 3 2 1 0}; set #0 {9 8 7 6 5}; set 0a {e d c b a}
% paranoid_iplsort data
0 1 2 3 4
% paranoid_iplsort #0
5 6 7 8 9
% paranoid_iplsort 0a
a b c d e

I know it may be a bit too late to change this, but I'd prefer that upvar use a different algorithm to determine whether its first argument is level or otherVar. I'll explain by way of demonstration:

rename upvar Upvar
proc upvar {args} {
    if {[llength $args] == 0} {
         # Received zero arguments.
         # Allow [upvar] to raise an error.
    } elseif {[llength $args] % 2 == 0} {
         # Received an even number of arguments.
         # Conclude that the first is a variable name.
         # Prepend $args with the default level, 1.
         set args [linsert $args 0 1]
    } else {
         # Received an odd number of arguments.
         # Conclude that the first is a level number.
    }

    # Let the real [upvar] do its thing.
    uplevel 1 Upvar $args
}

And a test:

% upvar 0 0a alpha
% set 0a success; puts $alpha
success
% proc test {} {upvar 0a beta; puts $beta}
% test
success

This should also work in the case of multiple variable pairs.

Ironically, I use uplevel 1 in the above code. uplevel has a similar problem: "Level cannot be defaulted if the first command argument starts with a digit or #." But there's no possibility of a cutesy workaround wrapper because it can legally accept any number of arguments, whereas upvar can only take an even number (not counting level).

So perhaps for the sake of consistency with uplevel I should just go ahead and put those blasted 1's in there anyway. :^)

Performance

IL: I'm curious about the performance implications of upvar recently. Is there a page discussing them? Passing a list vs. upvaring it for example. I had always assumed that any pass in Tcl was by reference, unless upvar is exactly that and everything else is by value?

RS: The Tcl way is indeed normally passing by value, which helps to make the code more robust (no side-effects possible). upvar is the way to pass by reference. The difference appears to be very little (or 50% more, if you take the Win XP timing to be precise):

proc a x {
    expr {$x*$x}
}
proc b _x {
    upvar 1 $_x x
    expr {$x*$x}
}
% time {set x 42; a $x} 100000
2 microseconds per iteration
% time {set x 42; b x} 100000
3 microseconds per iteration

PYK 2014-08-14: Coming to Tcl from other language traditions, one of the things that took longest to sink in was how Tcl's copy-on-write semantics eliminate any performance penalty of its pass-by-value semantics in most cases. It is precisely because of copy-on-write that the normal pattern in Tcl is to pass the value to procedures that won't modify it, and to pass the name of the variable to procedures that will. This issue also illustrates that comparing Tcl to other operating systems is sometimes more apt than comparing it to other programming languages.

Lars H 2008-08-21: When implementing the Man or Boy Test in Tcl a while back, I was surprised to see that the implementation using upvar

proc A {k x1 x2 x3 x4 x5} {
    expr {$k<=0 ? [eval $x4]+[eval $x5] : [B \#[info level]]}
}
proc B {level} {
    upvar $level k k x1 x1 x2 x2 x3 x3 x4 x4
    incr k -1
    A $k [info level 0] $x1 $x2 $x3 $x4
}
proc C {val} {return $val}
interp recursionlimit {} 1157
A 10 {C 1} {C -1} {C -1} {C 1} {C 0}

was actually about 30% faster than the "purer" implementation passing as much as possible by value (and using uplevel for the side-effect required by the test):

proc AP {k x1 x2 x3 x4 x5} {expr {$k<=0 ? [eval $x4]+[eval $x5] : [BP \#[info level] $x1 $x2 $x3 $x4]}}
proc BP {level x1 x2 x3 x4} {AP [uplevel $level {incr k -1}] [info level 0] $x1 $x2 $x3 $x4}
proc C {val} {return $val}
interp recursionlimit {} 1157
AP 10 {C 1} {C -1} {C -1} {C 1} {C 0}

This is probably due to dragging along more data in the latter case. (I doubt it is a shimmering effect, since failure to share Tcl_Objs should lead to an exponential overhead.)

Discussion

From Tcl Language Usage Questions And Answers , by Joseph Moss, I see:

Use upvar rather than try to use global variables when possible. If the function is event driven, you are forced to use global variables.

the statement "you are forced to use global variables" is a bit misleading. It's not that upvar doesn't work in event driven programs, it's just that functions called from events are run at the global level, rendering upvar relatively useless. If you have a callback that calls another proc, that second proc can use upvar with impunity


ZB: According to the documentation,

The variable named by otherVar need not exist at the time of the call; it will be created the first time myVar is referenced, just like an ordinary variable.

OK, so let's try following short example, also taken from the docs (proc add2 name { upvar $name x ; set x [expr {$x + 2}] }):

% proc add2 varname {upvar $varname x; set x [expr {$x + 2}]}
% add2 b
can't read "x": no such variable
% puts $b
can't read "b": no such variable
% 

Isn't it contrary to that remark "the variable doesn't need to exist, it will be created"? $b hasn't been created. Pay attention, that variable myVar ("x" in example above) has been referenced. Although I agree, that it looks like: "take something non-existing, and add a value of 2 to this" - but still: variable x has been referenced.

PYK 2014-03-24: A variable can be created but not defined. upvar creates the variable when it is referenced, but doesn't necessarily define it. info exists only returns 1 if the variable is both created and defined.

#shows that the variable is not created
namespace which -variable ::greeting ;#-> {}
proc myproc {varname} {
    upvar $varname var
    #reference the variable without defining it
    trace add variable var read {;#}
}
myproc greeting

#the variable now exists
namespace which -variable ::greeting ;#-> ::greeting

#and yet, it doesn't :)
info exists ::greeting ;#-> 0

See Also

variable
like upvar, links variables into the current execution frame or namespace, but functions a little differently
namespace upvar
like upvar but links variables into a namespace rather than into the current call frame.
Guidelines for using upvar and uplevel ,by Rob Mayof
the result of an exchange on the AOLserver mailing list. Some of the information at the mentioned site seems to be incorrect. Especially in reference to uplevel. I didn't look at in great detail however. Does this advice simply reflect best practice or what can be done?