tcl 切换名称空间和 proc 参数

tcl switching namespaces and proc arguments

我有一个小问题,目前无法解释。我创建了一个简约的代码片段来展示我的问题或不了解 tcl 名称空间的工作原理。

所以我有文件 test.tcl:

namespace eval test {
    proc print {file_name} {
        namespace inscope ::test2 {
            printFileName $::test::file_name
        }
    }
}

namespace eval test2 {
    proc printFileName {file_name} {
        puts $file_name
    }
}

比起我使用 tclsh 并运行:

source test.tcl
test::print test.dat

返回:

can't read "::test::file_name": no such variable

为什么 test::print 的参数不应该在 ::test 名称范围内? 我有一个简单的解决方法 set ::test::filename $filename before namespace inscope {}.

但是我不满意,因为我在这里错过了一些东西。

我不能只运行 ::test2::printFileName $file_name 因为我的真实世界代码更复杂并且不只运行一个命令它会获取一个列表全部位于不同命名空间中的命令。

这里的问题是 ::test::file_name 引用 ::test 命名空间中的 file_name 变量而 不是 局部变量 file_name 就像你想要的那样。这就是变量名称解析的工作方式。作为命令参数的局部变量不驻留在封闭的命名空间中。

此外,在脚本中直接调用 namespace inscope 并不常见。我会这样写:

namespace eval test {
    proc print {file_name} {
        {*}[namespace eval ::test2 namespace code printFileName] $file_name
    }
}

namespace eval test2 {
    proc printFileName {file_name} {
        puts $file_name
    }
}

如果您的 Tcl 版本中没有 {*},或者一些变体。

但是您最后关于无法 运行 命令的评论让我怀疑这种方法可能还不能解决您的问题。

局部变量不是命名空间变量。特别是,即使有变量链接(upvarglobalvariable 等的机制)形式参数变量在任何情况下都是 never 命名空间变量方法。这就是变量的映射方式,因为这样做非常快;过程入口代码是Tcl实现代码中最热门的部分之一,因此努力保持尽可能快。

但并非一无所有!

您可以很容易地将值复制到名称空间变量,甚至可以使用跟踪和 upvar 来使它看起来像局部变量可以从该内部范围写回。 (您可能必须使用 info level 在堆栈中搜索备份注入写入的位置,这将是 可怕的 混乱,但它会起作用。)

但我要做的是发出命令来提供值(并可能允许在必要时回写)。我觉得比较干净

namespace eval test {
    proc print {file_name} {
        proc ::test2::file_name {} [list return $file_name]
        namespace eval ::test2 {
            printFileName [file_name]
        }
    }
}

namespace eval test2 {
    proc printFileName {file_name} {
        puts $file_name
    }
}

您可以通过使用 namespace path 使它更优雅,这样您就不必在每次调用时都构建一个全新的过程。 interp alias 也有帮助(如果您熟悉函数式编程中的那种事情,这是进行参数柯里化的好方法)。