如何在运行时动态修改proc主体?

时间:2014-02-12 18:49:09

标签: dynamic tcl

我原则上想做这样的事情:

#grab some value from outer source (i.e. file or list defined by another programer)
set original_proc_name foo

#define function with a specific name using the string from that outer source
proc "helper_[set original_proc_name]" {val} {
    #I need to use that string inside the body of the new proc
    return [[set original_proc_name] $val]
}

我基本上需要神奇地控制proc主体,好像它是[list . . .]

有没有办法在运行时动态定义proc主体?

我希望这样做是因为我需要定义一堆用于测试的过程,这些过程基本上只是调用其他程序员用特定参数编写的一堆过程。

为什么要定义新的过程?因为这就是我们设置的回归所期望的(要运行的过程的名称)。

4 个答案:

答案 0 :(得分:4)

在Tcl中,没有什么是特别神圣的。几乎任何其他东西都可以使用语言中的任何东西。特别是,您可以使用任何字符串生成命令(实际上所有这些命令,尽管有些只生成空字符串)以生成过程的主体; proc只是一个普通的Tcl命令(碰巧发出其他Tcl命令,尽管还有其他命令也这样做。)

在您的具体情况下,您可能最好使用不同的方法。

set original_proc_name foo

interp alias {} helper_$original_proc_name {} apply {{cmd val} {
    $cmd $val
}} $original_proc_name

好的,这看起来有点神奇。这不是真的。让我们从内到外工作。

apply是一个有效地像无名程序一样工作的命令。它的第一个参数是一个列表(通常有两个元素),它定义了“类过程”事物和主体的参数。它的后续参数是实际的论点。因此,

apply {{a b} {
    puts "a=$a and b=$b"
}} 123 456

将打印a=123 and b=456。我们正在使用具有两个参数并将一个应用于另一个的特定主体;第一个参数是命令名(大概),第二个参数是发送给该命令的值。我们走了:

apply {{cmd val} {
    $cmd $val
}} puts "Hi"

我们确实得到了一段非常平凡的代码。哪个好处?

好吧,我们在这里使用interp alias。这种特殊形式:

interp alias {} foo {} bar boo

使得当你使用foo作为命令时,Tcl将调用交给bar boo,附加到foo的任何额外参数。在一个简单的例子中:

interp alias {} writeError {} puts stderr

为我们提供了一个命令,可以将消息写入stderr,如下所示:

writeError "GRUMPY CAT WANTS NO PART OF THIS!"

好的,所以我们将这两件事放在一起。

interp alias {} helper_$original_proc_name {} apply {{cmd val} {
    $cmd $val
}} $original_proc_name

这里,我们正在创建的命令别名是通过字符串替换生成的,我们正在执行内部简单调用程序的部分应用程序,以便将其锁定到原始命令名称。结果是我们使helper_foo成为一个只接受一个参数的命令,并将该参数传递给原始的foo进行处理。

并且没有复杂的字符串替换。

这种情况可能有点过分,但对于更复杂的任务,它比生成正确的脚本简单得多。它也比我更有点矫枉过正,我将在我自己的代码中使用它:

interp alias {} helper_$original_proc_name {} $original_proc_name

但这种语义略有不同。


在Tcl 8.6中,您还获得了tailcall。这可以使事情更加“有趣”。强大的力量带来了不负责任的大好机会......

答案 1 :(得分:1)

Glenn Jackman的具体例子(回答问题):

proc helper_$original_proc_name {val} "return \[$original_proc_name \$val]"

我设法用双引号完成了这个:

set some_val 7
set new_proc_body "puts $some_val \nputs \[expr $some_val * 2\]"
proc bar {} $new_proc_body
bar

这是另一个证明原则的例子:

set lst {dog strong bong}
foreach iter $lst {proc "super_[set iter]" {} "puts \"my name is $iter\" \nputs \"Yay $iter is great!\""}
super_dog

答案 2 :(得分:1)

你已经回答了自己的问题,大概是为了让你满意。如果您打算探索编写Tcl进一步编写Tcl,那么仍有一些事情可能有助于思考。有一件事是要记住,名字很少是特殊的,例如,例如,没有必要害怕名称list

set list {dog strong bong}

有时会指出,调用

set set set

是完全有效的Tcl,不会破坏任何东西。

另一件事是proc 的主体是一个列表(以及一个字符串,一个字符串列表和一个列表列表),可以对待因此。所以这样可行,并且可以避免大量的反斜杠:

foreach iter $list {
    proc super_$iter {} [join [list \
        [list puts "my name is $iter"] \
        [list puts "Yay $iter is great!"]] \n]
}
另一种方式:

foreach iter $list {
    lappend body \
        [list puts "my name is $iter"] \
        [list puts "Yay $iter is great!"]
    proc super_$iter {} [join $body \n]
    set body {}
}
另一个:

set outputs {{my name is $iter} {Yay $iter is great!}}
foreach iter $list {
    set body [lmap output $outputs { list puts [subst $output] }]
    proc super_$iter {} [join $body \n]
}

在Tcl中,代码是数据,数据可以是代码。你可以用一个字符串或你可以用proc主体做的列表做任何事情。

答案 3 :(得分:0)

我的方法是简单地检索现有的 proc 主体,修改脚本并重新定义 proc。

proc myProc {} {
    puts ABC
}

输出 ABC

set body [info body myProc]
append body "puts DEF"

proc myProc {} $body 

myProc

输出 美国广播公司 防御