TCL : array is a collection of variables - tcl

I am trying to understand the difference between TCL arrays and TCL dictionary
There I get a statement :- "TCL array is a collection of variables while TCL dictionary is a collection of values"
Can somebody explain what it actually means. Programming example to understand the same will be great
Thanks

With an array, these are actually three separate variables.
set my_array(a) 1
set my_array(b) 2
set my_array(c) 3
Each variable exists separately:
info exists my_array(a) --> 1
info exists my_array(b) --> 2
info exists my_array(c) --> 3
puts "$my_array(a) $my_array(b) $my_array(c)" --> 1 2 3
The name my_array is also recognized to exist, but is actually a special name for the collection of the three individual variables. It's not really a regular variable that has its own value.
info exists my_array --> 1
puts $my_array --> can't read "my_array": variable is array
With a dictionary, there is one variable. The value of the variable represent the key/value pairs.
set my_dict "a 1 b 2 c 3"
info exists my_dict --> 1
info exists my_dict a --> wrong # args: should be "info exists varName"
puts $my_dict --> {a 1 b 2 c 3}
dict keys $my_dict --> {a b c}
dict get $my_dict a --> 1
dict values $my_dict --> {1 2 3}
In this example, the value of my_dict is {a 1 b 2 c 3}. You can pass $my_dict as an argument to a proc and the value is used by the proc. You can also return a dict from a proc. You cannot do that with an array in the same way.

One of the big differences is that you can trace add variable on an element of an array, whereas you can pass a dictionary into a procedure and return it from a procedure without needing to fiddle around with upvar or array get/array set.
proc didSetOnVarElement {name1 name2 op} {
puts "did $op on ${name1}($name2)"
}
trace add variable x(a) write didSetOnVarElement
set x(a) 1
set x(b) 2
incr x(a)
# Try comparing the output of that with what you get when you set a trace on the whole of x
Tracing array elements can be really useful, especially in Tk. So can passing things back and forth cheaply. You can't have them together though: values don't have identity in Tcl, whereas variables do (and it is exactly that identity — that name — that makes them modifiable and traceable entities while preventing them from being passed around trivially).

In addition to Chris's excellent answer:
A dictionary can be passed to a proc as a value
proc printItByValue {value} {
puts $value
}
set myDict [dict create a 1 b 2 c 3] ;# just another way to create a dict
printItByValue $myDict ;# => a 1 b 2 c 3
An array must be passed to a proc differently:
array set myArray {a 1 b 2 c 3} ;# just another way to populate an array
printItByValue $myArray ;# error as shown by Chris
printItByValue myArray ;# only prints the string "myArray"
We could extract the arrays contents and pass that to a proc as a list:
proc printArrayByValue {arrayContents} {
array set localArray $arrayContents
parray localArray
}
printArrayByValue [array get myArray]
localArray(a) = 1
localArray(b) = 2
localArray(c) = 3
parray is a handy utility proc to pretty-print an array.
Or, we can use the upvar command to link the passed variable name to the caller's stackframe.
proc printArrayByName {varName} {
upvar 1 $varName localArray
parray localArray
}
printArrayByName myArray ;# same output as above
Note that these procs print out the array using the local name. To get the array to print with the same name, we can do this tricky thing to link the local variable to the caller's variable with the same name:
proc printArrayByName {varName} {
upvar 1 $varName $varName
parray $varName
}
printArrayByName myArray ;# same output as above
myArray(a) = 1
myArray(b) = 2
myArray(c) = 3

Related

Unable to get the array information in tcl loop

I am unable to print the desired 1 and 2 value of the variables in the foreach loop below. I am getting the name of the variable instead.
#A_voltages has 1
#B_voltages has 2
#port_1 has A
#port_2 has B
foreach v1 $port_1\_voltages {
foreach v2 $port_2\_voltages {
puts "-D3- v1=$v1= v2=$v2="
}
}
EXPECTED RESULT:
-D3- v1=1 v2=2
CURRENT RESULT:
-D3- v1=A_voltages v2=B_voltages
I'm not sure why you're trying to use nested loops, or loops at all if your variables have a single value each, but the trick is to build a variable name and pass it to set.
Example tclsh REPL session:
% set A_voltages 1
1
% set B_voltages 2
2
% set port_1 A
A
% set port_2 B
B
% puts "-D3 -v1=[set ${port_1}_voltages]= =[set ${port_2}_voltages]="
-D3 -v1=1= =2=
Note use of ${name} to set it apart from the rest of the string.
You may need to use the "array get", as in
#! /usr/bin/tclsh
set A_rray(0) "X"
set A_rray(1) "Y"
set A_rray(2) "Z"
set B_rray(0) "A"
set B_rray(1) "B"
set B_rray(2) "C"
set B_rray(3) "D"
foreach L { "A" "B" } {
puts $L
foreach {akey aval} [array get $L\_rray ] {
puts "$L $aval ($akey)"
}
}
which gives
A
A X (0)
A Y (1)
A Z (2)
B
B A (0)
B B (1)
B C (2)
B D (3)
It is often easiest to use upvar inside a procedure for this sort of thing. That lets you give variables easy-to-use names for the duration of the procedure. (Use #0 instead of 1 to get global names, or use namespace upvar :: instead of upvar.)
proc iterOver {port_1 port_2} {
upvar 1 ${port_1}_voltages p1v ${port_2}_voltages p2v
foreach v1 $p1v {
foreach v2 $p2v {
puts "-D3- v1=$v1= v2=$v2="
}
}
}
iterOver A B
For one-off code, a lambda can be used (with apply) as an alternative.

TCL : How can I check info for an array if array name is a variable too

I am not able to check if my array exists, the array name is in a variable. In my case array name contains space too.
code snippet:
array set a\ b{
key1 "val1"
}
proc checkArr {name}
{
if {![info exists $name($key)]} {
return {}
}
}
checkArr "a b"
error : can't read "name(key1)": variable isn't array
Can anyone help?
The main issue with your code is that you are trying to see if an array exists in a local scope while it exists in a global scope.
array set a\ b {
key1 "val1"
}
proc checkArr {name} {
upvar $name arr
if {![info exists arr(key1)]} {
return 0
} else {
return 1
}
}
In order to have the array accessible in the local scope of the proc, you can use either global $name or upvar $name arr (I used the latter above), or you can use uplevel when executing the info exist part.
Some other adjustments I carried out to the code:
Added space to the array set part, otherwise you are supplying only one parameter to the function
Changed $name($key) to arr(key1). info exists takes a variable name, so if you use $name($key), it will try to find the variable name for the value of that variable (if the value of $name($key) was 'value', info exists will try to find out if the variable named value exists, but the array value $name($key) doesn't exist, so you get the error).
Let's create a command to check for the existence of a global array:
proc checkArr name {
uplevel #0 [list info exists $name]
}
% checkArr "a b"
0
This command, when given "a b" as an argument executes the script info exists {a b}, but not at the local level inside the command: the uplevel #0 part means that the script is executed at the global level.
Now we create the array, and check it:
% array set a\ b {key1 val1}
% checkArr "a b"
1
It exists. We can also check the existence of a member of the array using the same code:
% checkArr "a b(key1)"
1
In fact, any kind of string can be passed to checkArr and it will tell you if there is a variable (array or otherwise) with that name. If you want a specialized checker for array members, it can be written like this:
proc checkArrayMember {arrName memberName} {
uplevel #0 [list info exists $arrName\($memberName)]
}
% checkArrayMember "a b" key1
1
The backslash in front of the left parenthesis takes away its syntactic meaning and makes it just a part of the string: the script essentially becomes info exists {a b(key1)}.
Documentation:
array,
info,
list,
proc,
uplevel,
Summary of Tcl language syntax

TCL proc for default value assignment

What is the syntax for a proc in tcl which automatically takes arguments if user din't give any arg?
I used something like
proc a {{args 1}} {
puts $args
}
a
when I used this I dint get my args value as 1. It returns a blank string. Please help me with this.
It sounds like you're trying to do two different things together:
Accept a variable number of arguments to your proc
If no arguments are provided, use default value(s)
There isn't really a syntax for proc that handles this case specifically. However, it's fairly easy to accomplish. You can accept a variable number of arguments (args keyword), and then check to see if any were supplied (and use a default value if not).
proc myproc {args} {
if { [llength [info level 0]] < 2 } { #called with no args
set args {the default list of values}
}
# rest of the code goes here
}
The info level 0 command returns the actual command being run as it was called, with arguments. Hence, if the length of it's result is < 2, we know the current command was called with no arguments (since the first element in the list is the actual command name itself).
The word args is a reserved word that has a specific meaning in proc definitions. Use a different variable name:
proc a {{x 1}} {
puts $x
}
a
Additional answer
In a proc definition, default values are given by defining the argument as a list with two members: the first will be the name of the argument, the second is the default value.
The word args is a special argument that implements rest arguments (that is, it captures remaining arguments not specified in the argument list). This is how one can implement variadic functions in Tcl.
The args arguments and default values can be used together when defining procs. But args cannot have default values. Any argument listed before args can have default values. But arguments that have default values must be listed after arguments without default values. So, basically you can write a function like this:
proc a {x {y 1} args} {
puts "$x $y ($args)"
}
a 1 ;# prints 1 1 ()
a 1 2 ;# prints 1 2 ()
a 1 2 3 4 ;# prints 1 2 (3 4)
If your use-case meets this pattern then you can define arguments with default values before args like the example above. If not, then your only option is to process args yourself:
# Assume parameters are key-value pairs,
# if value not given then default to 1
proc b args {
foreach {x y} $args {
if {$y == ""} { set y 1 }
puts "$x -> $y"
}
}
b 1 2 ;# prints 1 -> 2
b 1 2 3 ;# prints 1 -> 2, 3 -> 1

Can we create a list of arrays and how?

I want to create a list and each element of it is an array, similarly to an array of structs in C language.
Can it be done in TCL and how if it can? thanks very much!
I did some try but it failed...
tcl>set si(eid) -1
tcl>set si(core) 0
tcl>set si(time) 0
tcl>lappend si_list "$si"
Error: can't read "si": variable is array
You can't create a list of arrays, but you can create a list of dicts which is functionally the same thing (a mapping from keys to values):
set mylist [list [dict create a 1 b 2] [dict create a 4 b 5]]
puts [dict get [lindex $mylist 1] a]
To do it as arrays you need to use [array get] and [array set] to change the array into a string:
set si(eid) -1
set si(core) 0
set si(time) 0
lappend si_list [array get si]
And to get it back out
array set newsi [lindex $si_list]
puts $newsi(eid)
dicts let you work on the {name value} lists directly.
One way to do this on versions of Tcl that don't include dict is to use upvar.
To do this, add the names of the array variables to your list:
set si(eid) -1
set si(core) 0
set si(time) 0
lappend si_list "si"
Then to get your array back, do this:
upvar #0 [lindex $si_list 0] newsi
puts $newsi(eid)
You could also use the ::struct::record package from tcllib for something like that.

how to pass variable arguments from one function to other in tcl

I want to pass variable arguments obtained in one function to other function but I am not able to do so. Function gets even number of variable arguments and then it has to be converted in array. Below is the example.
Procedure abc1 gets two arguments (k k) and not form abc1 procedure these have to be passed to proc abc where list to array conversion it to be done. List to array conversion works in proc1 i.e. abc1 but not in second proc i.e. abc
Error obtained is given below
proc abc {args} {
puts "$args"
array set arg $args
}
proc abc1 {args} {
puts "$args"
array set arg $args
set l2 [array get arg]
abc $l2
}
abc1 k k
abc k k
Output:
k k
{k k}
list must have an even number of elements
while executing
"array set arg $l1"
(procedure "abc" line 8)
invoked from within
"abc $l2"
(procedure "abc1" line 5)
invoked from within
"abc1 k k"
(file "vfunction.tcl" line 18)
Best Solution: Expansion Substitution
The right approach is to ensure that the outer procedure (in stack terms) calls the inner one correctly; if multiple arguments are expected, that's what should be supplied. With the advent of Tcl 8.5, that's trivially done with a little language syntax called an expansion substitution:
proc abc1 {args} {
puts "$args"
array set arg $args
set l2 [array get arg]
abc {*}$l2
# Or combine the two lines above into: abc {*}[array get arg]
}
All the {*} does is say that the rest of the word should be broken up (using list syntax rules) and used as multiple arguments instead of Tcl's default “one visual word forms a single word” rules. It's ideal for this.
Old Solution: Eval Command
If you're still using old versions of Tcl for some reason (i.e., Tcl 8.4 or before) then you use the eval command instead of the above syntax:
eval abc $l2
There are some somewhat-more-efficient approaches to the above eval, which you might see in older code; for example:
eval [linsert $l2 0 abc]
eval [list abc] [lrange $l2 0 end]
# ... etc ...
But really they're all rendered obsolete by abc {*}$l2 which is shorter, simpler to write, and faster. (It's just not available in 8.4 or before, and too many deployments have yet to upgrade.) Use the expansion syntax if you can. Indeed, idiomatic Tcl code for 8.5 and later hardly ever needs eval; the extent to which this has proved true has even been rather surprising to the language's maintainers.
There's a big difference between
abc k k
and
abc [array get arg]
In the first case, you're passing two arguments, each of which is k. In the second case you're passing a list of things - in your example, a list of two k's: k k.
Nir's answer knowingly hacks around this problem, but the better solution is to write abc1 so that it calls abc properly:
proc abc1 {args} {
array set arg $args
set l2 [array get arg]
eval abc $l2
# or just
# eval abc $args
}
When you pass abc $l2 you are passing abc1's args as a single argument to abc. So in abc args holds a list with a single item ({k k}).
You can do something like this:
proc abc {args} {
#the next line assumes that if $args has only a single item, then it is
#a list in itself. It's a risky decision to make but should work here.
if { [llength $args] == 1 } { set args [lindex $args 0] }
puts "$args"
array set arg $args
}