What purpose does upvar serve? - tcl

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.

Related

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
}
...
}

How to count repeated words from the list

I have a list of cells,
U1864
u_dhm_lut/U4
u_dhm_lut/lut_out_reg_2_
u_dhm_lut/lut_in_reg_2_
And I want to calculate how many times each name comes
Result will:
U1864 1
u_dhm_lut/lut_out_reg_2_ 18
u_dhm_lut/lut_in_reg_2_ 14
u_dhm_lut/U4 10
The code is like:
set cell_cnt [open "demo.txt" r]
set cell [read $cell_cnt]
set b [open "number_of_cell.txt" w+]
proc countwords {cell_count} {
set unique_name [lsort -unique $cell_count]
foreach count $unique_name {
set cnt 0
foreach item $cell_count {
if {$item == $count} {
incr cnt
}
}
puts $b "$count :: $cnt"
}
}
countwords $cell
It says can't read "b":no such variable while executing
"puts $b "$count :: $cnt""
Why am i not able write a file inside proc?
Code inside a procedure scope can't use variables defined outside that scope, e.g. global variables. To be able to use global variables, you can import them into the procedure scope:
proc countwords cell_count {
global b
or use a qualified name:
puts $::b ...
You can also bypass the issue by passing the file handle to the procedure:
proc countwords {b cell_count} {
...
countwords $b $cell
or move the code for opening the file inside the procedure (not recommended: procedures should have one job only).
Old answer, based on the question title
This is one of the most frequently asked frequently asked questions. If you look a while back in the question list, you will find quite a few answers to this.
The solution is actually pretty easy, and the core of it is to use an array as a frequency table, with the words as keys and the frequencies as values. The incr command creates new entries (with a value of one) in the table as needed.
foreach word $words {
incr count($word)
}
The result is similarly easy to check:
parray count
The result can of course also be used in a script in any way that an array can be used.
Documentation:
array,
foreach,
incr,
parray
You can use the open file code i.e "set b [open "number_of_cell.txt" w+]" inside the method. This should also solve your problem

tcl proc using upvar resulting in history(nextid)

I'm getting this weird issue.
i'm using tcl 8.3
after i define this proc in a tcl shell
% proc incr { varName {amount 1}} {
puts $varName
upvar #0 $varName var
puts $varName
if {[info exists var]} {
set var [expr $var + $amount]
} else {
set var $amount
}
return $var
}
i keep getting
%
history(nextid)
history(nextid)
history(oldest)
history(oldest)
%
Everytime i hit newline "Enter" after that
any one has any idea why this is happening?
Because the history managment is written in Tcl itself, and that uses incr.
Your incr is almost equal to Tcl 8.3's incr with some differences:
The name of the variable is always printed
If the variable does not exist, it will be created.
So if you remove the first difference (the puts) everything will work as expected, just that some library commands may call your incr instead the standard incr.
The second difference is now in the core, IIRC starting with Tcl 8.5 it is not nessencary that a variable already exists pior to calling incr.
In short: What you did is fine. But don't expect to be the only one who calls an standard command.

SWIG + TCL flags

Will the ownership of a pointer last only in the block in which we set the -acquire flag for it?
Eg.:
{
{
$xyz -acquire
}
}
Firstly, Tcl doesn't define blocks with {/}. The scope is defined by the procedure call or namespace.
Secondly, Tcl commands are always defined to have lifetime that corresponds to the namespace that owns them; they are never† scoped to a procedure call. They must be manually disposed one way or another; there are two ways to do this manual disposal: calling $xyz -delete or rename $xyz "" (or to anything else that is the empty string). Frankly, I prefer the first method.
But if you do want the lifespan to be tied to a procedure call, that's actually quite possible to do. It just requires some extra code:
proc tieLifespan args {
upvar 1 "____lifespan handle" v
if {[info exists v]} {
trace remove variable v unset $v
set args [concat [lindex $v 1] $args]
}
set v [concat Tie-Garbage-Collect $args]
trace add variable v unset $v
}
proc Tie-Garbage-Collect {handles var dummy1 dummy2} {
upvar 1 $var v
foreach handle $handles {
# According to SWIG docs, this is how to do explicit destruction
$handle -delete
# Alternatively: rename $handle ""
}
}
That you'd use like this in the scope that you want to tie $xyz's life to:
tieLifespan $xyz
# You can register multiple objects at once too
And that's it. When the procedure (or procedure-like entity if you're using Tcl 8.5 or later) exits, the tied object will be deleted. It's up to you to decide if that's what you really want; if you later disown the handle, you probably ought to not use tying.
† Well, hardly ever; some extensions do nasty things. Discount this statement as it doesn't apply to SWIG-generated code!

what are the practical differences between upvar and global commands in 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.