tags:

views:

766

answers:

4

I'm fairly new to TCL, and am providing QA on some code developed by others (no really!). There are lots and lots of global variables in this particular program, and I sometimes see upvar used, often in conjunction with global. I understand that upvar emulates pass-by-reference, but what would be the practical difference be between the two following procs?

set myBigFatGloblVariable "hello"

proc myFirstProc { var1 var2 } {
    upvar 1 $var1 local
    set local [expr $var2 * 3]
}

proc mySecondProc { var2 } {
    global myBigFatGlobalVariable
    set $myBigFatGlobalVariable [expr $var2 * 3]
} 

myFirstProc $myBigFatGlobalVariable 3
mySecondProc 3

It seems to me that myFirstProc would be cleaner and . Am I missing something here?

+1  A: 

The difference is that upvar 1 $var local makes local take its value from the variable named in $var from the level above. So in myBigFatGlobalVariable $var does not have to be defined at the global scope.

proc p1 { var1 } {
upvar 1 $var1 local1
puts $local1
}

proc p2 { } {
set local2 "local2"
p1 local2
}

set global1 "global1"
p1 global1
p2

p1 will print out the value of var1 from the level 1 above it in the call stack. A global is always defined at the top level so upvar #0 does the same thing as global.

Jackson
Thanks! I think the answer is that in this case, there's no real difference, since both procs are being called at the same level. If mySecondProc were to call myFirstProc, I see the difference.
Andy Michaels
+2  A: 

They are similar but subtly different.

upvar allows you access variables up x levels in the call stack. They don't necessarily need to be global variables.

You can use upvar to emulate global by passing upvar #0 varName localVarName You will get the global variable with a local name in that case.

To emulate pass by reference, you are pass the name of the variable, then call upvar on that name.

If you know the name of the variable, you can use it as is.

Observe the following code:

    # here there is only 1 global variable, but we also need to access to variables defined in the calling functions
    proc p3 {} {
        # upvar defaults to 1, so not needed to put in here
        # also notice you can call upvar on more than one variable
        upvar dog myDog horse myHorse cat myCat
        upvar 2 cow myCow alpha myAlpha
        upvar #0 samurai mySamurai
        puts "Level 1: $myDog $myHorse $myCat"
        puts "Level 2: $myCow $myAlpha"
        puts "Global : $mySamurai"
    }
    proc p2 {} {
        set dog "bowow"
        set horse "niegh"
        set cat "meow"
        p3

    }
    proc p1 {} {
        set cow "moo"
        set alpha "beta"
        p2
    }

    set samurai "japan"
    p1

This returns

    Level 1: bowow niegh meow
    Level 2: moo beta
    Global : japan

upvar is just a way to get at variables from the call stack. (calling functions) including the 'global' stack.

Byron Whitlock
+1  A: 
set myBigFatGlobalVariable "hello"

proc myFirstProc { var1 var2 } {
    upvar 1 $var1 local
    set local [expr $var2 * 3] }

proc mySecondProc { var2 } {
    global myBigFatGlobalVariable
    set $myBigFatGlobalVariable [expr $var2 * 3] } 

myFirstProc $myBigFatGlobalVariable 3
mySecondProc 3

The big difference between your two procs is this: myFirstProc sets the global "hello", mySecondProc sets the local "hello".

mySecondProc references the global myBigFat... to get the value "hello", but does not alter the scope of the "hello" variable.

myFirstProc receives the value "hello" as a parameter, and then creates a link between a variable named "hello" one frame up the stack and the local variable "local". Setting "local" has the effect of setting "hello" one frame up the stack.

To see:


myFirstProc $myBigFatGlobalVariable 3
puts $hello ;# ==> 9
unset hello
mySecondProc 3
puts $hello ;# ==> can't read "hello": no such variable

If you really want to set the global "hello" from mySecondProc, you'll need to add global $myBigFatGlobalVariable

glenn jackman
Thanks, Glenn. This was exactly the explanation I was looking for!
Andy Michaels
A: 

You are saying:

There are lots and lots of global variables in this particular program

My experience with medium to very large Tcl applications (20k+ lines!) is that using namespaces will significantly help getting structure within the large amount of global variables.

The nice part is, is that you can add them iteratively everytime you create a new module to your code, or by refactoring some of your code.

namespace eval module1 {
  variable counter
  variable name
}

namespace eval module2 {
  variable n
  variable names
}

You can refer to them via module1::counter (just as you can refer to a global variable as ::counter

See the wiki namespace page and the Tcl manual page on namespaces for more information on namespaces.

Roalt