tclupvar

What purpose does upvar serve?


In the TCL code that I currently work on, the arguments in each procedure is upvar'ed to a local variable so to speak and then used. Something like this:

proc configure_XXXX { params_name_abc params_name_xyz} {
    upvar $params_name_abc abc
    upvar $params_name_xyz xyz
}

From here on, abc and xyz will be used to do whatever. I read the upvar TCL wiki but could not understand the advantages. I mean why cant we just use the variables that have been received as the arguments in the procedure. Could anybody please elaborate?


Solution

  • I mean why cant we just use the variables that have been received as the arguments in the procedure.

    You can. It just gets annoying.

    Typically, when you pass the name of a variable to a command, it is so that command can modify that variable. The classic examples of this are the set and incr commands, both of which take the name of a variable as their first argument.

    set thisVariable $thisValue
    

    You can do this with procedures too, but then you need to access the variable from the context of the procedure when it is a variable that is defined in the context of the caller of the procedure, which might be a namespace or might be a different local variable frame. To do that, we usually use upvar, which makes an alias from a local variable to a variable in the other context.

    For example, here's a reimplementation of incr:

    proc myIncr {variable {increment 1}} {
        upvar 1 $variable v
        set v [expr {$v + $increment}]
    }
    

    Why does writing to the local variable v cause the variable in the caller's context to be updated? Because we've aliased it (internally, it set up via a pointer to the other variable's storage structure; it's very fast once the upvar has been done). The same underlying mechanism is used for global and variable; they're all boiled down to fast variable aliases.

    You could do it without, provided you use uplevel instead, but that gets rather more annoying:

    proc myIncr {variable {increment 1}} {
        set v [uplevel 1 [list set $variable]]
        set v [expr {$v + $increment}]
        uplevel 1 [list set $variable $v]
    }
    

    That's pretty nasty!

    Alternatively, supposing we didn't do this at all. Then we'd need to pass the variable in by its value and then assign the result afterwards:

    proc myIncr {v {increment 1}} {
        set v [expr {$v + $increment}]
        return $v
    }
    
    # Called like this
    set foo [myIncr $foo]
    

    Sometimes the right thing, but a totally different way of working!

    One of the core principles of Tcl is that pretty much anything you can do with a standard library command (such as if or puts or incr) could also be done with a command that you wrote yourself. There are no keywords. Naturally there might be some efficiency concerns and some of the commands might need to be done in another language such as C to work right, but the semantics don't make any command special. They all just plain commands.