Question

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?

Était-ce utile?

La solution 3

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
    }

Autres conseils

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?\""
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top