what are the practical differences between upvar and global commands in tcl - tcl

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?

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.

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

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.

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.

Related

How to access a variable defined in proc 'a' from a different proc 'b' which does not call proc 'a'?

I am trying to execute a tcl script which makes exclusive calls to procs a and b. The two procs are not related to each other.
proc a {} {
set var1 "a"
}
proc b {} {
# Do something here with: $var1
}
# script.tcl
a
b
I do not have access to the script.tcl as well. When proc 'a' is called, I need to store the var1 somehow such that I can access it later within proc 'b' when it is called. How can I get the value of var1 in proc b? Doesn't seem like I can use 'global' and 'upvar' here?
A simple way is to define the variable in the global scope by preceding the variable name with ::
proc a {} {
set ::var1 "a"
}
proc b {} {
puts $::var1
}
Other methods would be to use the global command in each proc or to define the variable in a special namespace of its own.
Using variable instead of global offers a bit more flexibility if namespaces are involved
namespace eval n {
proc a {{value A}} {
variable var1
set var1 $value
return
}
proc b {} {
variable var1
puts "var1 is <$var1>"
}
}
n::a
n::b ;# => var1 is <A>
namespace eval n {a 42; b} ;# => var1 is <42>
puts $::var1 ;# => can't read "::var1": no such variable

How to reference a variable within a proc in another proc

I have a proc that evaluates an expr and appends to a particular list locally
proc a {} {
set mylist ""
set out [expr...
lappend mylist $out
}
I want to use the "mylist" list outside of the "a" proc without declaring it as global or without returning that list from within the proc using "return mylist". How do I go about doing that. I have two use cases, Use the variable within another proc:
proc b {} {
do something with the "mylist" from proc a
}
Use case 2 :
Just use it outside the proc [Not within another proc]
The "mylist" variable only exists as long as proc a is being executed. Whenever a proc finishes, all its local variables are cleaned up.
As long as a is in progress, you can access its variables using the upvar command.
For example: if you call b from a, b can access "mylist" using:
upvar 1 mylist othervar
puts $othervar
However, it is usually better practice to pass the variable (or at least its name) between procs, or make it a global or namespace variable.
Reference: https://www.tcl-lang.org/man/tcl/TclCmd/upvar.htm
Sample code snippet:
proc foo {ref_var} {
upvar $ref_var local_var
# do some operatins
lappend local_var 20
}
proc bar {} {
set a [list 10]
puts "Before: $a"
foo a
puts "After: $a"
}
# nested proc
bar
# without proc
set c [list 30]
puts "Initial: $c"
foo c
puts "Final: $c"
Output:
Before: 10
After: 10 20
Initial: 30
Final: 30 20

Tcl : pass all variables from main to procedure

In my tcl script there is a part of the code that is repeated a lot, so I want to make a procedure out of it.
The thing is this part uses dozens of variables, which I would like to avoid passing as arguments to the procedure. Is there a way to make all variables visible to the procedure? (Practically I want the "main" to branch to the procedure like a "goto" and then return and continue in main).
Edited: It does not need to be a procedure, feel free to suggest other ways to do this. The important part is not need to declare all variables/arguments passing from main to the function/procedure.
Example:
proc dummy_proc {} {
set var1 $var2
set var2 $var3
}
set var2 2
set var3 3
dummy_proc
puts "$var1 $var2"
# should print "2 3"
This is possible but generally not advisable due to the fact that it can make code harder to read (there's no direct indication where the variables come from or how variable values suddenly change). However in some cases this can reduce repetitive code.
Use upvar (https://www.tcl.tk/man/tcl8.6/TclCmd/upvar.htm):
proc dummy_proc {} {
upvar var1 v1
upvar var2 v2
upvar var3 v3
set v1 $v2
set v2 $v3
}
What upvar does is create a variable in local scope that references another variable in the caller's scope.
Alternatively you can also try using uplevel (https://www.tcl.tk/man/tcl8.6/TclCmd/uplevel.htm):
proc dummy_proc {} {
uplevel {
set var1 $var2
set var2 $var3
}
}
What uplevel does is similar to upvar but instead of creating variable references it actually executes code in the caller's scope. It's as if you temporarily go back to the caller function without returning, execute some code and come back. Because you execute code in the caller's scope all variables visible in the caller's scope is visible in the code you upleveled. Uplevel behaves almost like a macro instead of a function.
Use global
proc dummy_proc {} {
global var2 var3
set var1 $var2
set var2 $var3
}
set var2 2
set var3 3
dummy_proc
puts "$var1 $var2"
If you have a lot of globals you want to pass, you can use some foreach, but than you'll have to have a way to find them all.
e.g. all globals are called GLOBAL_<SOMEARG>
proc dummy_proc {} {
foreach glb [info globals GLOBAL_*] {
global $glb
}
...
}

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?
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.
The upvar command will allow you to modify a variable in a block and make this modification visible from parent block.
Try this:
# a function that will modify the variable passed
proc set_upvar { varname } {
upvar 1 $varname var
puts "var was $var\n"
set var 5
puts "var is now $var\n"
}
# a function that will use the variable but that will not change it
proc set_no_upvar { var } {
puts "var was $var\n"
set var 6
puts "var is now $var\n"
}
set foo 10
# note the lack of '$' here
set_upvar foo
puts "foo is $foo\n"
set_no_upvar $foo
puts "foo is $foo\n"
As it was mentioned in comment above, it is often used for passing function arguments by reference (call by reference). A picture costs a thousand words:
proc f1 {x} {
upvar $x value
set value 0
}
proc f2 {x} {
set x 0
}
set x 1
f1 x
puts $x
set x 1
f2 x
puts $x
will result in:
$ ./call-by-ref.tcl
0
1
With upvar we changed variable x outside of function (from 1 to 0), without upvar we didn't.

how do I update a variable via a tk window by name

Consider the following situation:
namespace eval ::mydialog {}
proc ::mydialog::show {w varName args} {
upvar 1 $varName theVar
# now I can access theVar
# (1)
# code defining/creating my window
# here some widgets for user interaction are created,
# some of which will call ::mydialog::_someCallback
wm protocol $w WM_DELETE_WINDOW [list ::mydialog::close $w]
}
proc ::mydialog::_someCallback {} {
# how do I access theVar here?
# (2)
}
proc ::mydialog::close { w } {
# here some changes are supposed to be written back into varName in the calling scope,
# how do I do that?!
# (3)
destroy $w
}
Im trying to figure out how to (a) get a variable from the calling scope (b) have it available in all three procs and (c) writing any changes back into said variable.
(a) I would normally solve using 'upvar 1 $varName theVar'
(b) I would normally solve with a namespace variable
(c) As long as we only have one proc that would happen automaticly with (a) due to the fact that we would be working on a local alias of that variable
The problem is that upvar only works (at least as intended) in (1).
I could use upvar in (1) and save/copy into a namespace variable, that would solve (a) and (b), but not (c).
I would be gratefull if someone could point me in the right direction here.
Also, as I'm relativly new to Tcl/Tk my concept might not be ideal, suggestions toward a better design are welcome too.
I suggest you use a namespace variable that keeps the name of the variable, and upvar using the global scope.
namespace eval ::mydialog {
variable varName
}
proc ::mydialog::show {w _varName args} {
variable varName $_varName
upvar #0 $varName theVar
}
proc ::mydialog::_someCallback {} {
variable varName
upvar #0 $varName theVar
puts $theVar
}
proc ::mydialog::close { w } {
variable varName
upvar #0 $varName theVar
set theVar newval
}
set globalvar oldval
# => oldval
::mydialog::show {} globalvar
::mydialog::_someCallback
# => oldval
::mydialog::close {}
# => newval
puts $globalvar
# => newval
Note that the syntax highlighting fails: #0 $varName theVar isn't really a comment.
This works with namespace variables too: if you have a variable called nsvar in the ::foobar namespace you can use it like this:
set ::foobar::nsvar oldval
::mydialog::show {} ::foobar::nsvar
::mydialog::_someCallback
::mydialog::close {}
puts $::foobar::nsvar
with the same effects.
You can't, however, use variables local to some procedure this way.
One way to make this really simple is to use Snit widgets instead of collections of Tcl procedures.
Documentation: namespace, proc, puts, set, upvar, variable
Snit documentation: man page, faq (the faq serves as a kind of introduction as well)