这是一个老问题,@BrianH 在这里的回答对机制非常透彻。但我想我会给出一个稍微不同的重点,作为一个“故事”。
在 Rebol 中,有一类称为 words 的类型。这些本质上是符号,因此它们的字符串内容被扫描并进入符号表。因此,"FOO" 将是一个字符串,而<FOO> 将是字符串的另一种“风味”,称为标签……FOO、'FOO、FOO: 和 :FOO 都是各种“风味”具有相同符号 ID 的单词。 (分别是一个“单词”、一个“lit-word”、一个“set-word”和一个“get-word”。)
被折叠成一个符号使得加载后无法修改单词的名称。与每个都有自己的数据并且是可变的字符串相比,它们被卡住了:
>> append "foo" "bar"
== "foobar"
>> append 'foo 'bar
** Script error: append does not allow word! for its series argument
不变性有一个优势,作为一个符号,它可以快速将一个词与另一个词进行比较。但是还有另一个难题:一个单词的每个实例都可以有一个不可见的属性,称为 binding。该绑定让它“指向”一个称为 context 的键/值实体,其中可以读取或写入值。
注意:与@BrianH 不同,我认为将此类绑定目标称为“上下文”并不是那么糟糕——至少我今天不这么认为。稍后再问我,如果有新的证据出现,我可能会改变主意。可以说它是一个类似对象的东西,但并不总是一个对象……例如,它可能是对堆栈上函数框架的引用。
谁将一个词带入系统,谁就能率先说出它所绑定的上下文。很多时候都是加载,所以如果你说load "[foo: baz :bar]" 并取回三字块[foo: baz :bar] 他们将被绑定到“用户上下文”,并回退到“系统上下文”。
遵循绑定就是一切的运作方式,每个单词的“风味”都有不同的作用。
>> print "word pointing to function runs it"
word pointing to function runs it
>> probe :print "get-word pointing to function gets it"
make native! [[
"Outputs a value followed by a line break."
value [any-type!] "The value to print"
]]
== "get-word pointing to function gets it"
注意:第二种情况没有打印该字符串。它探测了函数规范,然后字符串只是评估中的最后一件事,所以它评估了那个。
但是,一旦您掌握了包含单词的数据块,绑定就是任何人的游戏。只要上下文中包含一个单词的符号,您就可以将该单词重新定位到该上下文。 (假设该块还没有被保护或锁定以防止修改......)
这个级联的重新绑定机会链是重点。由于 FUNC 是一个“函数生成器”,它接受一个规范和一个你给它的主体,它有能力获取主体的“原始物质”及其绑定并覆盖它决定的任何一个。或许很怪异,但看看这个:
>> x: 10
>> foo: func [x] [
print x
x: 20
print x
]
>> foo 304
304
20
>> print x
10
FUNC 收到了两个块,一个代表参数列表,第二个代表正文。当它得到主体时,prints 都绑定到本机打印功能 (在这种情况下 - 重要的是要指出,当您从控制台以外的地方获取材料时,它们都可以以不同的方式绑定!)。 x 绑定到用户上下文(在这种情况下),它的值是 10。如果 FUNC 没有做任何事情来改变这种情况,事情就会保持这种状态。
但它把图片放在一起并决定,由于参数列表中有一个 x,它会查看正文并覆盖带有 x 符号 ID 的单词和一个新的绑定...本地的功能。这是它没有用x: 20 覆盖全局的唯一原因。如果您在规范 FUNC 中省略了 [x],则不会执行任何操作,并且会被覆盖。
定义链中的每个部分在传递事物之前都有机会。因此定义范围。
有趣的事实:因为如果你不向 FUNC 的规范提供参数,它不会重新绑定正文中的任何内容,这导致了错误的印象,即 “Rebol 中的所有内容都在全局范围内”时间>。但这根本不是真的,因为正如@BrianH 所说:“Rebol 实际上根本没有作用域(...)Rebol 伪造它。” 事实上,这就是 FUNCTION(与 FUNC 相对)确实——它在正文中寻找像 x: 这样的集合词,当它看到它们时将它们添加到本地框架并绑定到它们。效果看起来像局部范围,但又不是!
如果想象这些带有不可见指针的符号在四处移动听起来有点像 Rube-Goldberg-esque,那是因为它是。对我个人而言,了不起的事情是它完全有效……而且我看到人们用它来做特技,你不会凭直觉认为可以使用这么简单的技巧。
恰当的例子:非常有用的 COLLECT 和 KEEP (Ren-C version):
collect: func [
{Evaluates a block, storing values via KEEP function,
and returns block of collected values.}
body [block!] "Block to evaluate"
/into {Insert into a buffer instead
(returns position after insert)}
output [any-series!] "The buffer series (modified)"
][
unless output [output: make block! 16]
eval func [keep <with> return] body func [
value [<opt> any-value!] /only
][
output: insert/:only output :value
:value
]
either into [output] [head output]
]
这个看起来不起眼的工具以以下风格扩展了语言(同样,Ren-C 版本...在 R3-Alpha 或 Rebol2 中替换 foreach for for-each 和 length? for length of)
>> collect [
keep 10
for-each item [a [b c] [d e f]] [
either all [
block? item
3 = length of item
][
keep/only item
][
keep item
]
]
]
== [10 a b c [d e f]]
我上面提到的最能理解定义范围的技巧。 FUNC 只会覆盖其参数列表中事物的绑定,而不会触及正文中的其他所有内容。所以发生的情况是,它将您传递给 COLLECT 的主体用作新函数的主体,并在其中覆盖 KEEP 的任何绑定。然后它将 KEEP 设置为一个函数,该函数在调用时将数据添加到聚合器。
在这里,我们通过 /ONLY 开关看到了 KEEP 函数在将块拼接到收集的输出中的多功能性(只有当我们看到长度为 3 的项目时,调用者才选择不拼接)。但这只是表面问题。它只是一个非常强大的语言功能——用户事后添加的——源自如此少的代码,几乎令人恐惧。当然还有更多的故事。
我在这里添加了一个答案,因为我已经为定义范围填写了一个重要的缺失链接,这个问题被称为“定义范围的返回”:
https://codereview.stackexchange.com/questions/109443/definitional-returns-solved-mostly
这就是为什么 <with> return 在规范中与 KEEP 并排的原因。它在那里是因为 COLLECT 试图告诉 FUNC 它想“使用它的服务”作为代码的绑定器和运行器。但身体已经由其他人在其他地方创作。因此,如果它有一个 RETURN,那么这个 RETURN 已经知道要返回到哪里。 FUNC 只是为了“重新调整”keep 的范围,但保留任何回报而不是添加自己的回报。因此:
>> foo: func [x] [
collect [
if x = 10 [return "didn't collect"]
keep x
keep 20
]
]
>> foo 304
== [304 20]
>> foo 10
== "didn't collect"
<with> return 使 COLLECT 能够足够聪明地知道在 FOO 的体内,它不希望返回反弹,因此它认为从参数只是 [keep] 的函数返回。
还有一点关于定义范围的“为什么”,而不是“什么”。 :-)