Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)
like image 442
vaichidrewar Avatar asked Dec 13 '22 17:12

vaichidrewar


2 Answers

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.

like image 165
Donal Fellows Avatar answered Mar 16 '23 00:03

Donal Fellows


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
}
like image 27
Trey Jackson Avatar answered Mar 16 '23 00:03

Trey Jackson