my_question
my_question

Reputation: 3235

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.

Upvotes: 1

Views: 305

Answers (2)

Donal Fellows
Donal Fellows

Reputation: 137667

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.

Upvotes: 0

Brad Lanam
Brad Lanam

Reputation: 5723

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

Upvotes: 0

Related Questions