how to pass variable arguments from one function to other in tcl - 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
}

Related

Is there a workaround to pass variable number of arguments without using args

Here is the code:
>cat /tmp/test_args.tcl
proc t1 {args} {
return $args
}
proc t2 {args} {
puts "t2:[llength $args]"
return
set len [llength $args]
if {$len == 1} {
proc_len_1 [lindex $args 0]
} elseif {$len == 2} {
proc_len_2 [lindex $args 0] [lindex $args 1]
} else {
proc_len_x $args
}
}
set tup3 [t1 1 2 3 4 5 6]
puts "before calling t2:[llength $tup3]"
t2 $tup3
t2 100
t2 100 200
Here is the output:
>tclsh /tmp/test_args.tcl
before calling t2:6
t2:1
t2:1
t2:2
I am using TCL 8.6.
You can see that before calling t2, $tup3 is a list, but proc t2 receives $tup3 as one single value, so instead of a list of values, proc t2 receives a list of list of values.
But the intention of proc t2, as see in the code after "return", is to deal with various number of arguments and based on the number of arguments it does different things. Now, calling t2 with a list variable and with a literal are treated same. This is the problem.
The only solution I can think of is, change
t2 $tup3
to
t2 {*}$tup3
But I have a restriction: $tup3 needs to stay same when it is passed to different proc. E.g. I can have such proc which also expects $tup3:
proc t3 {arg1} {
}
t3 $tup3
So ideally if somehow I can make it that "args" does not wrap values into a list, then my problem is solved. Well, I know this is how TCL works.
Maybe I already answered my own question, or I do not know what the I am looking for. If you see indeed there is a solution, please let me know.
Thanks.
If you want to pass a list around, simply accept it as an argument:
proc a { mylist } {
b $mylist
}
proc b { mylist } {
foreach {k} $mylist {
puts $k
}
}
set tup3 [t1 1 2 3 4 5 6]
a $tup3
Edit:
For a variable number of arguments, using command line processing is easiest.
proc a { args } {
array set arguments $args
if { [info exists arguments(-tup3)] } {
puts "tup3: $arguments(-tup3)"
}
if { [info exists arguments(-tup1)] } {
puts "tup1: $arguments(-tup1)"
}
parray arguments
}
set tup3 [list 1 2 3 4 5 6]
a -tup1 test1 -tup3 $tup3 -tup2 test2
Tcl, by design, makes it very difficult for a procedure (or C-defined command) to examine the syntax of how it was called. It's totally deliberate that it is that way, as it makes it massively easier to compose commands arbitrarily. Commands that need to care especially about the syntax of how they're called are recommended to perform an extra step to process their argument, with appropriate calls to do things in the environment of the caller (trivial in C, slightly trickier in Tcl procedures because of the extra stack frame).
proc example inputString {
# Parse the string and work out what we want to do
if {[regexp {^\$(\w+)$} $inputString -> varName]} {
upvar 1 $varName value
} else {
set value $inputString
}
# Do something with the result
puts "my input string was '$inputString'"
puts "my value is '$value'"
catch {
puts "its length is [llength $value]"
}
}
example {foo bar boo}
set x 123
example {$x}
This prints:
my input string was 'foo bar boo'
my value is 'foo bar boo'
its length is 3
my input string was '$x'
my value is '123'
its length is 1
You can get the calling syntax inside your procedure, but this is highly unrecommended except for debugging as it tends to produce information that is usually annoying to process. Here's how you get it:
proc example inputString {
puts "I was called as: [dict get [info frame -1] cmd]"
}
# To show why this can be awkward, be aware that you get to see *all* the details...
example {foo bar boo}
example "quick brown fox"
example [expr {1 + sqrt(rand())}]
set x 123
example $x
Which prints:
I was called as: example {foo bar boo}
I was called as: example "quick brown fox"
I was called as: example [expr {1 + sqrt(rand())}]
I was called as: example $x
The first approach above, passing in a literal that you parse yourself (with appropriate help from Tcl as required) is considered to be good Tcl style. Embedding a language inside Tcl (which can be Tcl itself, or some other language; people have shown this working with embedded C and Fortran, and there's no reason to expect any other language to be a big problem, though getting useful evaluation semantics can sometimes be… tricky) is absolutely fine.

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

Is it possible to pass a function with multiple fixed arguments to ::math::optimize::min_bound_1d in Tcl

I'm fairly new to Tcl and I'm stuck on an assignment/project I have to do.
So I have a function F(x, A1, A2,...) = y. x is a free variable and A1, A2 etc. are some constants that will be known in advance. I have to find x for when y is a minimum for multiple values of A1, A2 etc.
I came across the function ::math::optimize::min_bound_1d which looks exactly like what I need (there are some constraints on the function). The problem is that this optimize function only seems to accept functions with 1 argument.
So in my sudo code below I have something like:
proc F {x arg1 arg1} {
# Crunch numbers
return $result
}
foreach arg1 $list_of_first_args {
foreach arg2 $list_of_second_args {
# Is it possible to pass $arg1 and $arg2 to the function below?
puts [::math_optimize_min_bound_1d F $lower_limit $upper_limit]
}
}
Is it possible in Tcl to pass/bound $arg1 and $arg2 to the function F in the example above so that I can get x when y is a minimum for each permutation of argN? Can I put all the arguments in a list and pass that?
Thanks!
Yes, provided you rearrange things a bit.
F needs to look like this, with the x variable placed last:
proc F {arg1 arg2 x} {
# Crunch numbers
return $result
}
(Or you can use a proxy command to rearrange argument order:)
proc F' {arg1 arg2 x} {
tailcall F $x $arg1 $arg2
}
Then, inside the foreach structures:
puts [::math::optimize::min_bound_1d [list F $arg1 $arg2] $lower_limit $upper_limit]
The [list F $arg1 $arg2] thingy is a kind of a poor man's lambda that quite a few higher-order function commands in Tcl accept. If, at evaluation, $arg1 is 1 and $arg2 is 12, the "function" {F 1 12} is passed into ::math::optimize::min_bound_1d and evaluated as [concat {F 1 12} $x].
A "real" lambda could look like this:
[list x [concat F \$x $arg1 $arg2]]
but this construct can only be used if the higher-order function command is prepared to evaluate it using apply $func $x.
Documentation: apply, join, list, math::optimize package, package, proc, puts, return, tailcall
Assuming that the code in question doesn't try to do funky things like parsing the source of the function to optimise, the easiest way is to make a proxy function:
proc proxy {value} {
global A1 A2
F $value $A1 $A2
}
Then just optimize the proxy.
Though you're going to have to deal with the fact that optimizing in higher dimensions is much more complex than in the single-dimension case.

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.

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