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
时,i
从 3
开始,加倍到 6
,然后 returned。因此,你得到 6
。第二次评估只是评估块,所以它使用 i
的当前值,即 6
,将其加倍并用结果回答,即 12
。再次计算 b
,您将得到 24
、48
等
另请注意,每次发送 #foo
时,它回复的块将评估为 6
。
附录
问题提到了编译器。好吧,编译器在脚本中做的很少。然而,在 #foo
中,它做了一些更复杂的事情,它使用代码 [i := i * 2. i]
创建了 BlockClosure
。块中未使用的临时变量存在于堆栈中,但此临时变量 i
不存在。为什么?因为block是一个会在方法执行后存活下来的对象,而方法一returns栈就没了。然后,该块将在 Array
中分配变量 i
,代表该块需要继续工作的环境。这个Array
不是在栈中分配的,而是在对象内存中分配的。这是块记住 i
的最后一个值的地方,并且可以根据需要多次更新和使用它,即使在调用 #foo
之后,它使用的堆栈也会被其他调用覆盖。 (是的,在 Smalltalk 中,编译器也是第一个 class 对象。)
在一些 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
时,i
从 3
开始,加倍到 6
,然后 returned。因此,你得到 6
。第二次评估只是评估块,所以它使用 i
的当前值,即 6
,将其加倍并用结果回答,即 12
。再次计算 b
,您将得到 24
、48
等
另请注意,每次发送 #foo
时,它回复的块将评估为 6
。
附录
问题提到了编译器。好吧,编译器在脚本中做的很少。然而,在 #foo
中,它做了一些更复杂的事情,它使用代码 [i := i * 2. i]
创建了 BlockClosure
。块中未使用的临时变量存在于堆栈中,但此临时变量 i
不存在。为什么?因为block是一个会在方法执行后存活下来的对象,而方法一returns栈就没了。然后,该块将在 Array
中分配变量 i
,代表该块需要继续工作的环境。这个Array
不是在栈中分配的,而是在对象内存中分配的。这是块记住 i
的最后一个值的地方,并且可以根据需要多次更新和使用它,即使在调用 #foo
之后,它使用的堆栈也会被其他调用覆盖。 (是的,在 Smalltalk 中,编译器也是第一个 class 对象。)