【问题标题】:strange behaviour of `eval` in TclTcl中`eval`的奇怪行为
【发布时间】:2013-09-03 07:28:32
【问题描述】:

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

数组mCallBackContinsert下的值为::postLayRep1||mainTableView sendToLoads,所以*

>>set mCallBackCont(insert)
::postLayRep1||mainTableView sendToLoads

其中::postLayRep1||mainTableView 是具有sendToLoads 公共方法的类的对象

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

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 时:

>>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} 对 procs 有效,但对类的方法无效(我猜这与 proc 是一个 1 字命令,而方法更复杂的事实有关)?

  • 我可以通过什么方式设置mCallBackCont(insert) 的值以使其正常工作?


* - 我试图将值放在mCallBackCont(insert) 中,既是一个列表,也是一个由""包围的字符串

【问题讨论】:

    标签: tcl


    【解决方案1】:

    首先,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 和第一个参数sendToLoads 的组合一样。它实际上是一个命名的部分应用程序。或者,您可以使用帮助程序:

    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 {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\"."
    }
    

    可以简化为:

    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{*}-expansion 实际上总是更接近预期。

    如果您坚持使用 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 8.4 的组织中更改一个没有明显所有者的库的代码。
    • 现在我无能为力了。如果您无法选择其他选项,则别名(我建议的第一件事)会起作用。
    猜你喜欢
    • 1970-01-01
    • 2018-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多