Tcl中'eval'的奇怪行为

Tcl中'eval'的奇怪行为,tcl,Tcl,我遇到了eval命令的一个奇怪行为,我无法理解。当我尝试使用eval运行一个名称存储在变量中的命令时,我得到了奇怪的结果 数组mCallBackCont在insert下具有值::postLayRep1 | | mainTableView sendtoods,以便*: 其中::postLayRep1 | | mainTableView是具有发送加载公共方法的类的对象 当我尝试执行以下操作之一时: eval {::postLayRep1||mainTableView sendToLoads} eva

我遇到了
eval
命令的一个奇怪行为,我无法理解。当我尝试使用eval运行一个名称存储在变量中的命令时,我得到了奇怪的结果

数组
mCallBackCont
insert
下具有值
::postLayRep1 | | mainTableView sendtoods
,以便*:

其中
::postLayRep1 | | mainTableView
是具有
发送加载
公共方法的类的对象

当我尝试执行以下操作之一时:

eval {::postLayRep1||mainTableView sendToLoads}
eval ::postLayRep1||mainTableView sendToLoads
eval "::postLayRep1||mainTableView sendToLoads"
eval $mCallBackCont(insert)
eval "mCallBackCont(insert)"
我得到了正确的行为,但当我使用

eval {$mCallBackCont(insert)}
我得到一个错误:

invalid command name "::postLayRep1||mainTableView sendToLoads"
当我在没有参数的常规进程中尝试相同的操作时:

>>proc test_proc {} {return}
>>set a test_proc
>>eval {$a}
一切正常,但当我添加参数时,同样的情况也会发生:

>>proc test_proc {val} {puts $val}
>>set a [list test_proc 1]
test_proc 1
>>eval {$a}
invalid command name "test_proc 1"
由于eval命令是我正在使用的库中代码的一部分,所以我无法更改它,我唯一能确定的是
mCallBackCont(insert)
的值。库中的代码是:

if { [catch {eval {$mCallBackCont(insert) [namespace tail $this] $type $name $n $redraw}} e] } {
          error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}
  • 为什么
    eval{$var}
    适用于proc而不适用于类的方法(我想这与proc是一个单字命令,而方法更复杂这一事实有关)

  • 如何设置
    mCallBackCont(insert)
    的值以使其正常工作



*-我已尝试将值放入
mCallBackCont(insert)
列表中,并将其作为一个由
包围的字符串。

首先,Tcl命令名中可以有空格。或者事实上几乎是任何其他角色;唯一需要注意的是
s,因为
是一个名称空间分隔符,前导的
::
可以,因为它只是表示它是一个完全限定的名称

因此,
::postLayRep1 | | mainTableView sendToLoads
是一个合法但不同寻常的命令名。如果将名称放入变量中,则可以使用从该变量读取的命令,就像它是命令名一样:

$theVariableContainingTheCommandName
在这方面,使用数组元素没有什么特别之处

现在,如果您想将其视为脚本,请将其传递给
eval
,如下所示:

eval $theVariableContainingTheScript
你面临的真正问题是你正在做:

eval {$theVariableContainingTheScript}
其定义与仅执行以下操作完全相同:

$theVariableContainingTheScript
那永远不会做你想做的事。查看导致问题的代码:

if { [catch {eval {$mCallBackCont(insert) [namespace tail $this] $type $name $n $redraw}} e] } {
    error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}
在这种情况下,变量中的值必须是命令名,而不仅仅是脚本片段。最简单的修复方法是创建一个别名,绑定额外的参数:

interp alias {} callBackForInsert {} ::postLayRep1||mainTableView sendToLoads
然后,您可以使用
callBackForInsert
,就像它是调用
::postLayRep1 | | mainTableView
与第一个参数
sendtooloads
的组合一样。它实际上是一个命名的部分应用程序。或者,您可以使用辅助程序:

proc callBackForInsert args {
    return [uplevel 1 {::postLayRep1||mainTableView sendToLoads} $args]
}
但在这个简单的例子中,这既丑陋又缓慢。在8.6中更好的是使用
tailcall

proc callBackForInsert args {
    tailcall ::postLayRep1||mainTableView sendToLoads {*}$args
}
但由于额外堆栈帧操作的开销,这仍然比别名慢


但是,最好的修复方法是修改库,使其使用如下回调(假设Tcl 8.5或更高版本):

可简化为:

if { [catch {{*}$mCallBackCont(insert) [namespace tail $this] $type $name $n $redraw} e] } {
    error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}
一个很好的经验法则是,在现代Tcl代码中几乎没有理由使用
eval
{*}
-扩展实际上总是更接近预期

如果您坚持使用8.4,但可以更改库代码,则可以改为:

if { [catch {eval $mCallBackCont(insert) {[namespace tail $this] $type $name $n $redraw}} e] } {
    error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}
这使用了这样一个事实,
eval
将在通过Tcl脚本求值引擎反馈参数之前连接参数



别名、扩展、
tailcall
和(本答案中未使用)集合的组合让您只需很少的代码就能完成出色的工作,允许复杂的参数混合,而无需加载大量的帮助程序。

首先,Tcl命令名中可以有空格。或者事实上几乎是任何其他角色;唯一需要注意的是
s,因为
是一个名称空间分隔符,前导的
::
可以,因为它只是表示它是一个完全限定的名称

因此,
::postLayRep1 | | mainTableView sendToLoads
是一个合法但不同寻常的命令名。如果将名称放入变量中,则可以使用从该变量读取的命令,就像它是命令名一样:

$theVariableContainingTheCommandName
在这方面,使用数组元素没有什么特别之处

现在,如果您想将其视为脚本,请将其传递给
eval
,如下所示:

eval $theVariableContainingTheScript
你面临的真正问题是你正在做:

eval {$theVariableContainingTheScript}
其定义与仅执行以下操作完全相同:

$theVariableContainingTheScript
那永远不会做你想做的事。查看导致问题的代码:

if { [catch {eval {$mCallBackCont(insert) [namespace tail $this] $type $name $n $redraw}} e] } {
    error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}
在这种情况下,变量中的值必须是命令名,而不仅仅是脚本片段。最简单的修复方法是创建一个别名,绑定额外的参数:

interp alias {} callBackForInsert {} ::postLayRep1||mainTableView sendToLoads
然后,您可以使用
callBackForInsert
,就像它是调用
::postLayRep1 | | mainTableView
与第一个参数
sendtooloads
的组合一样。它实际上是一个命名的部分应用程序。或者,您可以使用辅助程序:

proc callBackForInsert args {
    return [uplevel 1 {::postLayRep1||mainTableView sendToLoads} $args]
}
但在这个简单的例子中,这既丑陋又缓慢。在8.6中更好的是使用
tailcall

proc callBackForInsert args {
    tailcall ::postLayRep1||mainTableView sendToLoads {*}$args
}
但由于额外堆栈帧操作的开销,这仍然比别名慢


但是,最好的修复方法是修改库,使其使用如下回调(假设Tcl 8.5或更高版本):

可简化为:

if { [catch {{*}$mCallBackCont(insert) [namespace tail $this] $type $name $n $redraw} e] } {
    error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}
一个很好的经验法则是,在现代Tcl代码中几乎没有理由使用
eval
{*}
-扩展实际上总是更接近预期

如果您坚持使用8.4,但可以更改库代码,则可以改为:

if { [catch {eval $mCallBackCont(insert) {[namespace tail $this] $type $name $n $redraw}} e] } {
    error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}