我原则上想做这样的事情:
#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主体?
我希望这样做是因为我需要定义一堆用于测试的过程,这些过程基本上只是调用其他程序员用特定参数编写的一堆过程。
为什么要定义新的过程?因为这就是我们设置的回归所期望的(要运行的过程的名称)。
答案 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
输出 美国广播公司 防御