There is a TCL script which has multiple procedure definitions with similar name func in different namespaces. Procedures look like this:
proc func {a} {
puts $a
}
All that kind of procedures have only one argument a . All that kind of procedures are called from one single line in whole script:
func $a
I need to create another procedure definition also with similar name func in other namespace. But that procedure will have two parameters. That procedure is also need to be called from the same line that other procedures with same name. Procedure looks like this:
proc func {a b} {
puts $a
puts $b
}
I need now to modify the line that calls all that procedures func $a so, that it can call all procedures with one parameter and new procedure which has two parameters. But procedures definitions with one parameter must not be changed. What line that calls all these procedures func $a should look like?
If you want an optional parameter, and you know what the optional value should be if not supplied, you do this:
proc func {a {b "the default"}} {
puts "a is $a"
puts "b is $b"
}
If you need to compute the default value at runtime, the simplest technique is a magic sentinel value that is very unlikely to occur in real input. Such as two ASCII NUL characters (== Unicode U+000000):
proc func {a {b "\u0000\u0000"}} {
if {$b eq "\u0000\u0000"} {
set b "default:$a"
}
puts "a is $a"
puts "b is $b"
}
Otherwise, you can use the magic args value to get the complete list of arguments and do all the work “by hand”:
proc func {a args} {
if {[llength $args] == 0} {
set b "the default..."
} elseif {[llength $args] == 1} {
set b [lindex $args 0]
} else {
error "bad number of arguments!"
}
puts "a is $a"
puts "b is $b"
}
If you're doing that, the info level introspector can help, but things can get complicated…
To call one of two implementations of a command depending on the number of arguments is rather unusual in Tcl code. You can do it providing neither implementation of the command is in the global namespace and you are not wanting the switching behaviour when calling from the namespaces containing the implementations in question.
What you do is you create a procedure in the global namespace (which every other namespace will look for commands in if not present locally) which then chains explicitly to the desired implementation. The main thing you need to enable this is some way of working out which implementation you want in a particular case (such as looking at the length of the argument list).
For Tcl 8.6, you can use tailcall for the chaining for maximum efficiency:
proc ::func args {
if {[llength $args] == 1} {
tailcall ::impl1::func {*}$args
} else {
tailcall ::impl2::func {*}$args
}
}
In Tcl 8.5 you'd write this instead (which is an optimised case in the interpreter):
proc ::func args {
if {[llength $args] == 1} {
return [uplevel 1 [list ::impl1::func {*}$args]]
} else {
return [uplevel 1 [list ::impl2::func {*}$args]]
}
}
In older Tcl versions, you'd use something like this (which is slower):
proc ::func args {
if {[llength $args] == 1} {
return [uplevel 1 ::impl1::func $args]
} else {
return [uplevel 1 ::impl2::func $args]
}
}
None of this is perfect at handling getting the right sort of error messages when you call with entirely the wrong number of arguments, especially if neither implementation formally has optional arguments. Determining that automatically is probably wholly impractical! You end up having to write extra boilerplate code (which is pretty obvious and works in all versions of Tcl in a straight-forward way):
proc ::func args {
if {[llength $args] == 1} {
tailcall ::impl1::func {*}$args
} elseif {[llength $args] == 2} {
tailcall ::impl2::func {*}$args
} else {
# Using the -errorcode is optional really
return -code error -errorcode {TCL WRONGARGS} \
"wrong # args: should be \"func a ?b?\""
}
}
I found the solution from that answer: https://stackoverflow.com/a/22933188/1601703 . We can get the number of argument that procedure accepts and make coresponding if statments that will use corresponding procedure call:
set num [llength [info args func]]
if {$num == 1} {
func $a
} elseif {$num == 2} {
func $a $b
}
Related
In the code below I want to take a variable number of arguments, even zero argument.
But the interpreter says wrong argument number error.
% interp alias {} func {} proc
% func var {varname = args} {
if {$args eq ""} {
set varname
} else {
upvar $varname v
set v $args
}
}
% var part1 = Sal
Sal
% puts $part1
Sal
% var part1
wrong # args: should be "var varname = ?arg ...?"
Why?
The arguments that you provide all get passed through to the procedure. The procedure has been declared by you to take three arguments, formally called varname, = and args (where the last one is special and receives a list of all remaining arguments). This means it has two mandatory arguments, and a completely unbounded number of optional arguments.
If you want = to be an optional argument, you have to give it a default value in the declaration, perhaps like this:
# I've put the value in quotes for highlighting purposes only
proc var {varname {= "="} args} {
(You can call it func if you want, but I'll use the standard name.)
If formal parameters have default values, they are considered to be optional when the procedure is called. However, they consume arguments before the final args does.
I recommend using [llength $args] == 0 to check for list emptiness, not $args eq ""; the latter forces a conversion-to-string and can be expensive.
Overall, this is much closer to idiomatic (and basically a long winded way of writing something almost like the standard set command.)
proc var {varname {= "="} args} {
upvar 1 $varname v
if {[llength $args]} {
set v $args
}
return $v
}
I am writing a proc to create a header in an output file.
Currently it needs to take an optional parameter, which is a possible comment for the header.
I have ended up coding this as a single optional parameter
proc dump_header { test description {comment = ""}}
but would like to know how I can achieve the same using args
proc dump_header { test description args }
It's quite easy to check for args being a single blank parameter ($args == ""), but doesn't cope well if passing multiple parameters - and I need the negative check anyway.
Your proc definition is incorrect (you'd get the error message too many fields in argument specifier "comment = """). Should be:
proc dump_header { test description {comment ""}} {
puts $comment
}
If you want to use args, you could examine the llength of it:
proc dump_header {test desc args} {
switch -exact [llength $args] {
0 {puts "no comment"}
1 {puts "the comment is: $args"}
default {
puts "the comment is: [lindex $args 0]"
puts "the other args are: [lrange $args 1 end]"
}
}
}
You might also want to pass name-value pairs in a list:
proc dump_header {test desc options} {
# following will error if $options is an odd-length list
array set opts $options
if {[info exists opts(comment)]} {
puts "the comment is: $opts(comment)"
}
puts "here are all the options given:"
parray opts
}
dump_header "test" "description" {comment "a comment" arg1 foo arg2 bar}
Some prefer a combination of args and name-value pairs (a la Tk)
proc dump_header {test desc args} {
# following will error if $args is an odd-length list
array set opts $args
if {[info exists opts(-comment)]} {
puts "the comment is: $opts(-comment)"
}
parray opts
}
dump_header "test" "description" -comment "a comment" -arg1 foo -arg2 bar
I use tcllib's cmdline library to do option parsing.
This is the example from cmdline documentation:
set options {
{a "set the atime only"}
{m "set the mtime only"}
{c "do not create non-existent files"}
{r.arg "" "use time from ref_file"}
{t.arg -1 "use specified time"}
}
set usage ": MyCommandName \[options] filename ...\noptions:"
array set params [::cmdline::getoptions argv $options $usage]
if { $params(a) } { set set_atime "true" }
set has_t [expr {$params(t) != -1}]
set has_r [expr {[string length $params(r)] > 0}]
if {$has_t && $has_r} {
return -code error "Cannot specify both -r and -t"
} elseif {$has_t} {
...
}
So, in your case, you'd just use args in place of argv in the above example.
It should be mentioned explicitly that args is a special word in Tcl that, when used at the end of the argument list, contains a list of all the remaining arguments. If no args are given, then no error is produced (unlike any other variable name, which would be considered a required argument).
I was looking for a way to have functionality similar to python's kwargs (optional key-value pair arguments), and something that works nicely is (similar to Glenn's last example):
proc my_proc {positional_required1 {positional_optional1 "a_string"} args} {
# Two optional arguments can be given: "opt1" and "opt2"
if {![string equal $args ""]} {
# If one or more args is given, parse them or assign defaults.
array set opts $args
if {[info exists opts(opt1)]} { set opt1 $opts(opt1) } else { set opt1 0 }
if {[info exists opts(op2)]} { set opt2 $opts(opt2) } else { set opt2 -1 }
} else {
# If no args are given, assign default values.
set op1 0
set op2 -1
}
# DO STUFF HERE
}
And can be called like:
my_proc "positional_required1_argument"
# OR
my_proc "positional_required1_argument" "a_string"
# OR
my_proc "positional_required1_argument" "a_string" opt1 7
# OR
my_proc "positional_required1_argument" "a_string" opt1 7 opt2 50
# etc.
A potential downside (as I've currently implemented it) is that if a user passes a non-approved key-value option, there is no error.
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.
Are there any packages or any specific way to support function or procedure overloading in TCL??
This is my scenario. I need to write a generic procedure that accepts two or 3 files, wherein I may or may not have the third file (File3)
proc fun { File1 File2 File3 }
{
}
proc fun { File1 File2 }
{
}
There is no overriding in tcl. The second declaration will just replace the first one.
But you handle it with a single procedure. There are two ways at least:
1) Specify the last argument with its default value. Then it will be optional when you calls the function.
proc fun { file1 file2 {file3 ""} } {
if {$file3 != ""} {
# the fun was called with 3rd argument
}
}
2) Use the special argument args, which will contain all arguments as a list. And then analyze the number of arguments actually passed to.
proc fun { args } {
if {[llength $args] == 3} {
# the fun was called with 3rd argument
}
}
Tcl doesn't really support procedure overloading, which makes sense when you consider that it doesn't really have types, per se. Everything is a string that can, depending on value, be interpreted as other types (int, list, etc).
If you can describe what it is you're trying to accomplish (why you think you need overloading), we might be able to make a recommendation about how to accomplish it.
Given the edit to your question, there's a couple different ways to go about it. GrAnd has shown 2 of them. A third, and one I'm a fan of, is to use information specifically about how the command was called:
proc fun { File1 File2 {File3 ""}} { ;# file3 has a default
if {[llength [info level 0]] == 3} { ;# we were called with 2 arguments
;# (proc name is included in [info level 0])
# do what you need to do if called as [fun 1 2]
} else { ;# called with 3 arguments
# do what you need to do if called as [fun 1 2 3]
}
}
Here is an example to hack puts, using a namespace to hide puts and :: to access built-in:
namespace eval newNameSpace {
proc puts {arg} {
set tx "ADDED:: $arg"
::puts $tx
}
puts 102
}
Another way, you can do this:
proc example {
-file1:required
-file1:required
{-file3 ""}
} {
if {$file3 ne ""} {
#Do something ...
}
}
when you call the proc
example -fiel1 info -file2 info2
I am writing a proc to create a header in an output file.
Currently it needs to take an optional parameter, which is a possible comment for the header.
I have ended up coding this as a single optional parameter
proc dump_header { test description {comment = ""}}
but would like to know how I can achieve the same using args
proc dump_header { test description args }
It's quite easy to check for args being a single blank parameter ($args == ""), but doesn't cope well if passing multiple parameters - and I need the negative check anyway.
Your proc definition is incorrect (you'd get the error message too many fields in argument specifier "comment = """). Should be:
proc dump_header { test description {comment ""}} {
puts $comment
}
If you want to use args, you could examine the llength of it:
proc dump_header {test desc args} {
switch -exact [llength $args] {
0 {puts "no comment"}
1 {puts "the comment is: $args"}
default {
puts "the comment is: [lindex $args 0]"
puts "the other args are: [lrange $args 1 end]"
}
}
}
You might also want to pass name-value pairs in a list:
proc dump_header {test desc options} {
# following will error if $options is an odd-length list
array set opts $options
if {[info exists opts(comment)]} {
puts "the comment is: $opts(comment)"
}
puts "here are all the options given:"
parray opts
}
dump_header "test" "description" {comment "a comment" arg1 foo arg2 bar}
Some prefer a combination of args and name-value pairs (a la Tk)
proc dump_header {test desc args} {
# following will error if $args is an odd-length list
array set opts $args
if {[info exists opts(-comment)]} {
puts "the comment is: $opts(-comment)"
}
parray opts
}
dump_header "test" "description" -comment "a comment" -arg1 foo -arg2 bar
I use tcllib's cmdline library to do option parsing.
This is the example from cmdline documentation:
set options {
{a "set the atime only"}
{m "set the mtime only"}
{c "do not create non-existent files"}
{r.arg "" "use time from ref_file"}
{t.arg -1 "use specified time"}
}
set usage ": MyCommandName \[options] filename ...\noptions:"
array set params [::cmdline::getoptions argv $options $usage]
if { $params(a) } { set set_atime "true" }
set has_t [expr {$params(t) != -1}]
set has_r [expr {[string length $params(r)] > 0}]
if {$has_t && $has_r} {
return -code error "Cannot specify both -r and -t"
} elseif {$has_t} {
...
}
So, in your case, you'd just use args in place of argv in the above example.
It should be mentioned explicitly that args is a special word in Tcl that, when used at the end of the argument list, contains a list of all the remaining arguments. If no args are given, then no error is produced (unlike any other variable name, which would be considered a required argument).
I was looking for a way to have functionality similar to python's kwargs (optional key-value pair arguments), and something that works nicely is (similar to Glenn's last example):
proc my_proc {positional_required1 {positional_optional1 "a_string"} args} {
# Two optional arguments can be given: "opt1" and "opt2"
if {![string equal $args ""]} {
# If one or more args is given, parse them or assign defaults.
array set opts $args
if {[info exists opts(opt1)]} { set opt1 $opts(opt1) } else { set opt1 0 }
if {[info exists opts(op2)]} { set opt2 $opts(opt2) } else { set opt2 -1 }
} else {
# If no args are given, assign default values.
set op1 0
set op2 -1
}
# DO STUFF HERE
}
And can be called like:
my_proc "positional_required1_argument"
# OR
my_proc "positional_required1_argument" "a_string"
# OR
my_proc "positional_required1_argument" "a_string" opt1 7
# OR
my_proc "positional_required1_argument" "a_string" opt1 7 opt2 50
# etc.
A potential downside (as I've currently implemented it) is that if a user passes a non-approved key-value option, there is no error.