在标准Tcl中,作用域是否可以私有化



我试着在另一个问题中问这个问题,但做得很差。第一块代码来自David Flanagan书中的JS示例。该示例的要点是,在getCounter()返回后,方法nextreset共享的作用域是私有的,代码的其余部分不再可以访问。换句话说,除非通过这两种方法,否则不能更改n

第二块代码来自John Ousterhout关于Tcl的书中的一个例子,它引用了名称空间。我并不是在暗示他的例子与范围有关;我只是想知道是否可以这样做。它的工作原理与JS示例大致相同,只是让过程更改num的值,但范围不是私有的。外部代码可以简单地set counter::num 10,这在JS示例中是无法做到的。至少似乎没有办法访问n

我的问题是,除了使用OOP Tcl库之外,是否还有一种方法可以像JS示例中那样使Tcl中的作用域私有?

谢谢。

"use strict";
function getCounter () {
var n = 0;
return {
next: function () { return ++n; },
reset: function () { n = 0; }
};
}           
var counter = getCounter();
console.log(counter.next()); // 1
console.log(counter.next()); // 2
console.log(counter.next()); // 3
counter.reset();
console.log(counter.next()); // 1
console.log(counter.next()); // 2
console.log(counter.next()); // 3
// Cannot access n from here because
// private scope.

namespace eval counter {
variable num 0
proc next {} {
variable num
return [incr num]
}
proc reset {} {
variable num
set num 0
}
}
chan puts [counter::next]; # 1
chan puts [counter::next]; # 2
chan puts [counter::next]; # 3
counter::reset
chan puts [counter::next]; # 1
chan puts [counter::next]; # 2
chan puts [counter::next]; # 3
set counter::num 10
chan puts [counter::next]; # 11
chan puts [counter::next]; # 12

不是真的,主要是因为tcl并没有真正的引用/指针(所有东西都是一个值)。你引用的js机制是闭包,如果没有引用(多个变量引用同一个对象),就不可能有闭包。看见https://wiki.tcl-lang.org/page/Closures进行长时间的讨论。

但tcl确实有一种大多数其他语言都没有的机制,可以用来限制访问——只是它不会是你正在考虑的那种逻辑。Tcl具有interp功能,能够从Tcl本身创建、删除、操作和执行整个Tcl解释器。tcl中有一个概念叫做安全解释器(很多关于这个主题的文章)。一个安全的解释器基本上是一个tcl解释器,您可以在其中删除所有不希望脚本访问的功能。

这是可能的,因为Tcl允许您重新定义或重命名函数/procs,包括内置函数。例如,如果您不希望脚本访问网络,您可以简单地删除socket命令。在您的情况下,您可以创建一个没有set函数的interp,从而无法创建或修改变量。当然,你自己也需要set函数来运行你自己的代码,所以你不必删除它,而是可以将其重命名为第三方猜不到的东西,比如笑脸表情符号(-这是一个完全合法的proc名称!)或一行3个NUL字符()。任何试图在对讲机中直接使用set命令的人都会触发错误。

以下内容需要Tcl 8.7

您可以在TclOO类中创建私有变量,这为它们提供了一个真实的名称";难以预测";并防止大多数篡改。

oo::class create Counter {
private variable num
constructor {} { my reset }
method next {} { incr num }
method reset {} { set num 0 }
}
Counter create counter
chan puts [counter next]; # 1
chan puts [counter next]; # 2
chan puts [counter next]; # 3
counter reset
chan puts [counter next]; # 1
chan puts [counter next]; # 2
chan puts [counter next]; # 3
# Can't do these; they don't work
# set counter::num 10
# chan puts [counter next]; # 11
# chan puts [counter next]; # 12

该机制与"安全灌篮"一样;私人的";字段,但实际上有点难以猜测(它使用一个内部ID,用于跟踪重命名和删除过程中的对象标识)。但是有一个名称,这意味着变量可以参与,比如vwait(通常如果对象告诉它)。

另一种方法是使用协同程序:

coroutine counter apply {{} {
set num 0
while true {
switch [yield $num] {
next { incr num }
reset { set num 0 }
stop { return }
default { yieldto error "I won't do that..." }
}
}
}}

在这种情况下,变量是一个局部变量,因此可以屏蔽大多数外部访问。(但在8.7中,您可以使用coroprobe来处理它;它是为调试此类场景而设计的。)

如果一个值绝对必须从用户代码中保留,那么它应该保存在不同的解释器或C扩展中。口译员的设计并不是要有实际的安全边界;他们走到了另一个层次。但对于像柜台这样简单的东西,通常麻烦并不重要。(当代码完全不可信时,或者在应用程序服务器场景中,使用多个解释器非常有意义。)

最新更新