Block 在 Squeak 中使用外部变量

Block uses an outer variable in Squeak

在一些 myClass class 我有:

foo
   |i b|
   i := 5.
   b := [i := i * 2. i].
   i :=3.
   ^b

我运行:

|a i b1 b2|
i := 4.
a := MyClass new.
b1 := a foo.
b2 := a foo.
Transcript show: b1 value; cr.
i := 1.
Transcript show: b1 value; cr.
Transcript show: b2 value; cr.

我不明白为什么输出是:

6
12
6

是否可以解释一下编译器是如何计算出来的?

您得到的是预期的行为。首先,脚本中定义的临时i,无论其名称如何,都与#foo方法中的临时i没有任何影响或关系,因此您可以从中删除它脚本。

其次,该方法以BlockClosure回答,即[i := i*2. i]。这意味着它会记住 i 的值,将其加倍,然后再 return。作为副作用,该方法还将 i 的值设置为 3.

然后,当您第一次计算 b 时,i3 开始,加倍到 6,然后 returned。因此,你得到 6。第二次评估只是评估块,所以它使用 i 的当前值,即 6,将其加倍并用结果回答,即 12。再次计算 b,您将得到 2448

另请注意,每次发送 #foo 时,它回复的块将评估为 6

附录

问题提到了编译器。好吧,编译器在脚本中做的很少。然而,在 #foo 中,它做了一些更复杂的事情,它使用代码 [i := i * 2. i] 创建了 BlockClosure。块中未使用的临时变量存在于堆栈中,但此临时变量 i 不存在。为什么?因为block是一个会在方法执行后存活下来的对象,而方法一returns栈就没了。然后,该块将在 Array 中分配变量 i,代表该块需要继续工作的环境。这个Array不是在栈中分配的,而是在对象内存中分配的。这是块记住 i 的最后一个值的地方,并且可以根据需要多次更新和使用它,即使在调用 #foo 之后,它使用的堆栈也会被其他调用覆盖。 (是的,在 Smalltalk 中,编译器也是第一个 class 对象。)