Rebol 编程/高级/绑定学
作者:Ladislav Mecir
我要感谢那些以某种方式影响了这篇文章的人。特别是 Galt Barber、Brian D.、Mark Dickson、Elan Goldman、Brian Hawley、Gregg Irwin、Thomas Jensen、Pierre Johnson、Holger Kruse、Volker Nitsch、Larry Palmiter、Patrick Philipot、Gabriele Santilli、Carl Sassenrath、Frank Sievertsen 和 Romano Paolo Tenca。任何错误都是我的版权。
本文中的代码在 Rebol/View 2.7.6.3.1 中测试过。其他版本的解释器可能产生不同的结果。
想要检查所有示例的读者可以运行这段代码
do http://www.rebol.org/download-a-script.r?script-name=contexts.r
,它定义了本文中的所有函数。
Rebol 词语像所有 Rebol 值一样具有类型。让我们看看所有可用的词语类型
type? first [rebol] ; == word! type? first [rebol:] ; == set-word! type? first [:rebol] ; == get-word! type? first ['rebol] ; == lit-word! type? first [/rebol] ; == refinement!
此外,所有 Rebol 词语都有一个通用的伪类型 ANY-WORD!
any-word? first [rebol] ; == true any-word? first [rebol:] ; == true any-word? first [:rebol] ; == true any-word? first ['rebol] ; == true any-word? first [/rebol] ; == true
每个词语都有一个拼写。词语的拼写是一个字符串,它是上面示例词语共有的属性之一。我们可以使用 TO-STRING 函数获取词语的拼写
to-string first [rebol] ; == "rebol" to-string first [rebol:] ; == "rebol" to-string first [:rebol] ; == "rebol" to-string first ['rebol] ; == "rebol" to-string first [/rebol] ; == "rebol"
观察(不寻常的拼写):词语通常不会有一些拼写,例如包含空格的拼写、以冒号开头的拼写等。另一方面,可以创建具有任何拼写的词语,如下所示
unusual: make word! ":unusual word:" type? unusual ; == word! to-string unusual ; == ":unusual word:"
观察(拼写和词语相等):拼写完全相同的词语是相等的。
说明
equal? first [rebol] first [rebol:] ; == true
反向蕴涵不成立(因为 Rebol 支持别名)。然而,我们至少可以以某种方式反转蕴涵。
观察(SAME? 和拼写):如果两个词语根据 SAME? 函数相同,那么它们具有完全相同的拼写。
词语的一个非常重要的属性是能够充当变量(引用 Rebol 值)。要设置一个变量 'rebolution 来引用 Rebol 字符串 "uprising",我们可以选择以下之一
rebolution: "uprising" set 'rebolution "uprising" set/any 'rebolution "uprising" set first [rebolution:] "uprising"
等等。
要获取变量引用的值,我们可以选择以下之一
:rebolution get 'rebolution get/any 'rebolution
等等。
观察(变量):词语充当变量的能力与一个称为绑定的词语属性密切相关。词语是变量当且仅当它被绑定到一个上下文(具有上下文,在上下文之中)。
说明
; a refinement get/any /rebol ** Script Error: rebol word has no context ** Near: get/any /rebol
找出词语是否是变量的最简单方法是使用 BIND? 函数的属性,该函数对于不是变量的词语返回 NONE
variable?: func [ {is the given WORD a variable?} word [any-word!] ] [ found? bind? :word ]
测试
variable? 'rebol ; == true variable? /rebol ; == false
观察(上下文唯一性):正如 BIND? 函数帮助我们发现的那样,对于每个 Rebol 词语,最多存在一个该词语被绑定到的上下文。
推论(上下文层次结构):从上面的观察结果可以得出,在 Rebol 中不存在上下文层次结构,因为上下文层次结构需要某个词语被绑定到至少两个不同的上下文,其中一个比另一个“更小”。
观察(词语相同):根据 SAME? 函数,两个词语相同当且仅当它们具有完全相同的拼写和相同的绑定。
观察(BIND? 函数的结果):当使用 BIND? 函数获取对象中词语的上下文时,BIND? 函数的结果“不被认为与”对象相同。
说明
o: make object! [a: none] o-context: bind? in o 'a same? o o-context ; == false
观察(相等的词语不一定具有相同的绑定):实际上,情况恰恰相反。对于任何词语,我们都可以创建一个具有完全相同的拼写、相同的类型和不同的绑定的词语。
different-binding: func [ { for a given WORD yield a word having strict equal spelling, equal type and different binding } word [any-word!] {the given word} ] [ bind :word use reduce [to word! :word] reduce [to lit-word! :word] ]
让我们测试一下该函数是否按我们声明的那样工作
word1: 'a ; == a word2: different-binding word1 ; == a strict-equal? to-string word1 to-string word2 ; == true equal? type? word1 type? word2 ; == true equal? bind? word1 bind? word2 ; == false set word1 1 set word2 2 get word1 ; == 1 get word2 ; == 2
测试表明 WORD1 和 WORD2 具有完全相同的拼写和相同的类型。它们的绑定不同。它们可以同时引用不同的值,因此它们是不同的变量。另一个测试
word1: /a ; == /a word2: different-binding word1 ; == /a same? word1 word2 ; == false equal? bind? word1 bind? word2 ; == false
当我们需要获得一个词语,该词语具有给定 WORDS 词语的拼写和类型,以及给定 KNOWN-WORD 的上下文时,我们可以使用 BIND 函数。
BIND 函数的工作原理如下
观察(绑定到无上下文):如果 KNOWN-WORD 没有上下文,BIND 会引发错误。
说明
a-word: second first context [rebol: 1] ; == rebol bind? a-word ; == none bind 'a a-word ** Script Error: rebol word has no context ** Near: bind 'a a-word
观察(有效绑定):如果可能,BIND 函数将产生一个词语,该词语具有 WORDS 参数的拼写和类型,以及 KNOWN-WORD 参数的上下文。
说明
words: 'a ; == a known-word: use [a b] ['b] ; == b result: bind words known-word ; == a equal? bind? known-word bind? result ; == true same? words known-word ; == false
我们观察到,结果具有 WORDS 词语的拼写和类型,但它不是 WORDS 词语,因为它被绑定到与 KNOWN-WORD 相同的上下文。
观察(相等的词语和有效绑定):如果 WORD1 是一个具有上下文 CONTEXT 的变量,而 WORD2 等于 WORD1,那么 WORD2 也可以被绑定到 CONTEXT。
观察(无效绑定):如果 BIND 函数无法产生一个词语,该词语具有 WORDS 参数的拼写和类型,以及 KNOWN-WORD 参数的绑定,那么 BIND 将返回 WORDS 参数。
words: 'c ; == c known-word: use [a b] ['b] ; == b result: bind words known-word ; == c same? words result ; == true
在这种情况下,BIND 只是返回了 WORDS 词语。
观察(不复制的绑定块):如果没有使用 /COPY 修饰,BIND 会用绑定结果替换块中的元素。此规则有一个例外:BIND 不会绑定块中包含的修饰。
定义(别名):我们称拼写不完全相同的等效词为别名。
这是我们翻译成 Rebol 的定义
aliases?: func [ {find out, if WORD1 and WORD2 are aliases} word1 [any-word!] word2 [any-word!] ] [ found? all [ equal? :word1 :word2 strict-not-equal? to-string :word1 to-string :word2 ] ]
推论(词语相等):当且仅当以下条件之一成立时,两个词语相等
- 词语的拼写完全相同
- 词语是别名
推论(别名和 SAME?):当比较两个别名时,SAME?函数返回 FALSE。
证明:参见 (SAME?和拼写) 观察和我们对别名的定义。
观察(自动别名):由于 Rebol 试图做到不区分大小写,解释器通常(除了不一致的情况)“认为”拼写仅在大小写方面不同的词语是别名。
观察(ALIAS):可以使用 ALIAS 函数显式定义别名。
; let's create an alias 'revolutionary for the word 'rebol alias 'rebol "revolutionary" ; 'rebol and 'revolutionary will be equal words with different spelling: equal? 'rebol 'revolutionary ; == true strict-equal? to-string 'rebol to-string 'revolutionary ; == false aliases? 'rebol 'mean ; == false aliases? 'rebol 'rebol ; == false aliases? 'rebol 'revolutionary ; == true aliases? 'system 'SYSTEM ; == true
观察(ALIAS 返回值):ALIAS 函数返回未绑定的词语。
y: alias 'x "xx" ; == xx bind? y ; == none
观察(变量一致性):当且仅当两个词语相等且它们的绑定也相等时,它们才是一个变量。
same-variable?: func [ {are WORD1 and WORD2 the same variable?} word1 [any-word!] word2 [any-word!] ] [ found? all [ equal? :word1 :word2 equal? bind? :word1 bind? :word2 ] ]
观察(ALIASES?的备选定义):根据我们的先前观察,此定义等效于我们最初的定义
aliases?: func [ {find out, if WORD1 and WORD2 are aliases} word1 [any-word!] word2 [any-word!] /local context ] [ found? all [ equal? :word1 :word2 ( if context: any [bind? :word1 bind? :word2] [ word1: in context :word1 word2: in context :word2 ] ; WORD1 and WORD2 have equal binding now not same? :word1 :word2 ) ] ]
备选定义看起来更复杂,但由于它不需要操作字符串,因此它更快。
BIND?函数允许我们找到给定词语的上下文。相反的任务是找到所有在给定 CONTEXT 上下文中的词语。它可以按照以下步骤完成
context-words?: func [ {get the words in a given CONTEXT} context [object!] ] [ bind first context context ]
观察(简化的上下文词语集):作为 first context
表达式的结果获得的块是简化的上下文词语集。与上述函数的结果相反,它包含未绑定的词语。此外,与上述函数的结果类似,它不包含其词语的别名,并且只包含 WORD!数据类型的词语。
说明
alias 'rebol "rebellious" o: make object! [rebellious: 1] first o ; == [self rebellious] bind? first first o ; == none in o 'rebol ; == rebol
定义(全局上下文):全局上下文可以定义为
global-context: bind? 'system
注意:这不是唯一的选项,另一个选项是将其定义为 SYSTEM/WORDS 对象。以上定义为我们提供了全局词语的最简单定义。
定义(全局词语/全局变量):绑定到全局上下文的词语我们称之为全局词语(全局变量)
global?: func [ {find out if a WORD is global} word [any-word! object!] ] [ same? global-context bind? :word ]
观察(MAKE、TO、LOAD、BIND 和全局上下文):由 MAKE WORD!、MAKE SET-WORD!、MAKE GET-WORD!、MAKE LIT-WORD!、MAKE REFINEMENT!、TO WORD!、TO SET-WORD!、TO GET-WORD!、TO LIT-WORD!、TO REFINEMENT!、LOAD 和 BIND WORD 'SYSTEM 创建的词语是全局的。
说明
global? make word! first first rebol/words ; == true global? to word! first first rebol/words ; == true
观察(自动增长):可以使用 MAKE、TO、LOAD 和 BIND 函数扩展全局上下文。另一方面,IN 函数不会扩展全局上下文。
观察(MAKE、TO 和未绑定的词语):如果 SPEC 参数是字符串,则 MAKE BLOCK!、TO BLOCK!及其子块的结果块中包含的所有词语都是未绑定的。
说明
bind? first make block! "unbound" ; == none bind? first first first make block! "[[unbound-too]]" ; == none
定义(局部词语/局部变量):我们称既不是未绑定也不是全局的词语为局部词语(局部变量)
local?: func [ {find out, if a WORD is local} word [any-word!] ] [ not any [ none? bind? :word global? :word ] ]
定义(局部上下文):如果一个上下文的词语是局部词语,则称该上下文为局部上下文。
观察(局部上下文类型):用户定义的对象、函数上下文和 USE 上下文是局部上下文。除了这些,我们还可以使用 BIND?函数将用户定义的对象和端口“转换为”上下文,并使用 DISARM 函数将错误转换为对象。所有结果都是局部上下文。函数和 USE 上下文与所有其他上下文类型之间的主要区别在于,函数和 USE 上下文不需要包含等于词语 'self 的词语。
观察(扩展局部上下文):局部上下文不可扩展。
观察(DIFFERENT-BINDING 函数的结果):DIFFERENT-BINDING 函数的结果(如我们上面定义的)始终是局部词语。
让我们观察一下 Rebol 解释器在评估示例代码字符串时的行为
code-string: {'f 'g 'h use [g h] [colorize "USE 1" 'f 'g 'h use [h] [colorize "USE 2" 'f 'g 'h]]}
COLORIZE 函数将代码列表着色如下
- 未绑定的词语将为 棕色
- 全局词语将为 蓝色
- 由第一个 USE 评估绑定的词语将为 红色
- 由第二个 USE 评估绑定的词语将为 洋红色
emit: func [text [char! string! block!]] [ append result either block? text [rejoin text] [text] ] colorize: func [ {emit a table row containing text and the colorized code block} text [string!] /local space? ] [ emit ["^/|-^/| " text "^/| "] space?: "" parse code-block rule: [ ( emit [space? #"["] space?: "" ) any [ [ set word any-word! ( emit [ space? {<font color="} case [ not bind? :word ["brown"] global? :word ["blue"] equal? bind? :word bind? code-block/6/4 ["red"] equal? bind? :word bind? code-block/6/8/5 [ "magenta" ] ] {">} mold :word </font> ] ) | into rule | set word skip ( emit [space? mold :word] ) ] (space?: " ") ] ] ]
让我们观察代码是如何被解释的
; the result will be a wikitable result: {^{| class="wikitable" border="1" |- ! Text ! Code} ; first, the interpreter creates a code block code-block: make block! code-string colorize "String to block conversion" ; next, the code block is bound to the global context bind code-block global-context colorize "Code block bound to the global context" ; and then the code block is interpreted do code-block ; now we close the table emit "^/|}^/" write clipboard:// result
结果(从剪贴板粘贴到这里)是
文本 | 代码 |
---|---|
字符串到块的转换 | ['f 'g 'h use [g h] [colorize "USE 1" 'f 'g 'h use [h] [colorize "USE 2" 'f 'g 'h]]] |
绑定到全局上下文的代码块 | ['f 'g 'h use [g h] [colorize "USE 1" 'f 'g 'h use [h] [colorize "USE 2" 'f 'g 'h]]] |
USE 1 | ['f 'g 'h use [g h] [colorize "USE 1" 'f 'g 'h use [h] [colorize "USE 2" 'f 'g 'h]]] |
USE 2 | ['f 'g 'h use [g h] [colorize "USE 1" 'f 'g 'h use [h] [colorize "USE 2" 'f 'g 'h]]] |
结果证明
- 在字符串到块转换之后,CODE-BLOCK 中的所有词语都是未绑定的
- 在 CODE-BLOCK 绑定到全局上下文之后,CODE-BLOCK 中的所有词语都是全局的
- 第一个 USE 调用用局部 USE 1 词语替换了其主体块及其子块中的所有 'g 和 'h 词语
- 第二个 USE 调用用局部 USE 2 词语替换了最内层块中的 'h
观察(计算绑定):在解释过程中,代码中包含的 Rebol 词语的绑定被改变(即词语被替换为具有不同绑定的词语),直到它们被正确绑定并评估。这就是为什么 Rebol 的创建者将这种行为称为“计算绑定”。
看起来我们在执行上面的 Rebol 代码时观察到一个“范围层次结构”。正如我们所演示的那样,这仅仅是计算绑定的副作用。
借助计算绑定,我们可以轻松创建不显示任何“范围”属性的代码示例
; create a block CODE-BLK containing a word 'a code-blk: copy [a] a: 12 ; now append another word 'a to CODE-BLK make object! [append code-blk 'a a: 13] code-blk ; == [a a] ; test if CODE-BLK contains equal words equal? first code-blk second code-blk ; == true ; prove that the CODE-BLK is not a "scope" equal? bind? first code-blk bind? second code-blk ; == false
CODE-BLK 示例表明,对于代码块来说,在 Rebol 中没有“当前上下文”这样的东西,因为在 Rebol 中只有单个词语与上下文相关联。
为了尽可能精确,我们将用 Rebol 编写 USE 函数行为的描述。
以下函数创建一个新上下文,其中所有单词都未设置
make-context-model: func [ {context creation simulation} words [block!] {context words, needs to be non-empty} ] [ bind? first use words reduce [reduce [first words]] ]
USE 的描述
use-model: function [ {USE simulation, works for non-empty WORDS block} [throw] words [block!] "Local word(s) to the block" body [block!] "Block to evaluate" ] [new-context] [ unless empty? words [ ; create a new context new-context: make-context-model words ; bind the body to the new Context bind body new-context ] do body ]
观察 (USE-MODEL 和 BODY): USE-MODEL 在将 BODY 参数绑定到新上下文时会修改它的 BODY 参数。如果我们想让 BODY 参数保持不变,我们应该使用 BIND/COPY 而不是当前的 BIND。
让我们比较 USE-MODEL 的行为和 USE 的行为
body: ['a] body-copy: copy body same? first body first body-copy ; == true use [a] body same? first body first body-copy ; == false
正如我们所确定的,USE-MODEL 和原始 USE 都是一样的。模拟非常准确,它帮助我们发现以下代码中的一个错误
f: func [x] [ use [a] [ either x = 1 [ a: "OK" f 2 a ] [ a: "BUG!" "OK" ] ] ] f 1 ; == "BUG!"
解释/修正
观察到的 USE 属性导致函数 F 的主体在第二次 USE 执行期间被修改。修改之后,它不再包含在第一次调用 F 期间设置为“OK”的单词“a”。相反,它只包含在第二次调用 F 期间设置为“BUG!”值的单词“a”。
如果我们以某种方式保留 F 的主体,我们可以获得正确的行为
f: func [x] [ use [a] copy/deep [ either x = 1 [ a: "OK" f 2 a ] [ a: "BUG!" "OK" ] ] ] f 1 ; == "OK"
另一种修正行为的方法是使用我们自己的 USE 版本,它不会修改它的主体参数
nm-use: func [ { Defines words local to a block. Does't modify the BODY argument. } [throw] words [block!] {Local words to the block} body [block!] {Block to evaluate} ] [ use words copy/deep body ]
我们需要一个函数来评估 SPEC 参数,就像 MAKE OBJECT! 一样,这意味着它必须捕获 RETURN、THROW 和 BREAK
spec-eval: func [ {evaluate the SPEC like MAKE OBJECT! does} spec [block!] ] [ any-type? catch [loop 1 spec] ]
MAKE OBJECT! 模拟
make-object!-model: function [ {MAKE OBJECT! simulation} spec [block!] ] [set-words object sw] [ ; find all set-words in SPEC set-words: copy [self] parse spec [ any [ copy sw set-word! (append set-words sw) | skip ] ] ; create a context with the desired local words object: make-context-model set-words ; set 'self in object to refer to the object object/self: object ; bind the SPEC to the blank object bind spec in object 'self ; evaluate it spec-eval spec ; return the value of 'self as the result return get/any in object 'self ]
观察 (MAKE-OBJECT!-MODEL 和 SPEC): MAKE-OBJECT!-MODEL 在将 SPEC 参数绑定到新上下文时会修改它的 SPEC 参数。如果我们想让 SPEC 参数保持不变,我们应该使用 BIND/COPY 而不是当前的 BIND。
描述的行为导致了类似于 USE 部分中描述的错误
f: func [x] [ get in make-object!-model [ a: "OK" if x = 1 [ a: "BUG!" f 2 a: "OK" ] ] 'a ] f 1 ; == "BUG!"
解释和修正与 USE 函数类似。在递归调用 F 后位置的 a: “OK” 行中绑定到首先创建的对象 F 的单词 a: 被绑定到递归调用期间创建的对象 F 的单词 a: 替换。因此,表达式 a: “OK” 对首先创建的对象 F 没有影响,因此它保留了 'a 的最后一个值,即“BUG!”。如果我们保留 F 的主体,我们可以获得正确的行为
f: func [x] [ get in make object! copy/deep [ a: "OK" if x = 1 [ a: "BUG!" f 2 a: "OK" ] ] 'a ] f 1 ; == "OK"
如您所见,上面的代码在将 BODY 块绑定到上下文之前深度复制了它。当 FUNC 函数创建 Rebol 函数时,如果未使用深度复制,就会发现类似的错误。
这是 MAKE 函数获取要创建的对象的原型的场景的模拟。首先,我们需要一个特殊的 BIND 类函数
specbind: function [ {bind only known-words} block [block!] known-words [block!] ] [p w bind-one kw] [ bind-one: [ p: [ copy w any-word! ( if kw: find known-words first w [ change p bind w first kw ] ) | copy w [path! | set-path! | lit-path!] ( if kw: find known-words first first w [ change p bind w first kw ] ) | into [any bind-one] | skip ] ] parse block [any bind-one] block ]
以下是模拟
make-proto: function [ {MAKE PROTO simulation} proto [object!] spec [block!] ] [set-words object sw word value spc body pwords] [ ; get local words from proto set-words: copy first proto ; append all set-words from SPEC parse spec [ any [ copy sw set-word! (append set-words sw) | skip ] ] ; create a blank object with the desired local words object: make-context-model set-words object/self: object ; copy the contents of the proto pwords: bind first proto object repeat i (length? first proto) - 1 [ word: pick next first proto i any-type? set/any 'value pick next second proto i any [ all [string? get/any 'value set in object word copy value] all [ block? get/any 'value value: specbind copy/deep value pwords set in object word value ] all [ function? get/any 'value spc: load mold third :value body: specbind copy/deep second :value pwords set in object word func spc body ] any-type? set/any in object word get/any 'value ] ] bind spec object spec-eval spec return get/any in object 'self ]
在我们尝试模拟函数评估之前,我们可以问一下我们是否可以使用与 CONTEXT 函数相同的方法来处理局部单词。
答案是肯定的,能够做到这一点的函数定义如下。
首先是一个函数,可以从函数的 SPEC 中提取所有局部单词
locals?: func [ {Get all locals from a spec block.} spec [block!] /args {get only arguments} /local locals item item-rule ] [ locals: make block! 16 item-rule: either args [[ refinement! to end (item-rule: [end skip]) | set item any-word! (insert tail locals to word! :item) | skip ]] [[ set item any-word! (insert tail locals to word! :item) | skip ]] parse spec [any item-rule] locals ] set-words: func [ {Get all set-words from a block} block [block!] /deep {also search in subblocks/parens} /local elem words rule here ] [ words: make block! length? block rule: either deep [[ any [ set elem set-word! ( insert tail words to word! :elem ) | here: [block! | paren!] :here into rule | skip ] ]] [[ any [ set elem set-word! ( insert tail words to word! :elem ) | skip ] ]] parse block rule words ] funcs: func [ {Define a function with auto local and static variables.} [throw] spec [block!] {Help string (opt) followed by arg words with opt type and string} init [block!] {Set-words become static variables, shallow scan} body [block!] {Set-words become local variables, deep scan} /local svars lvars ] [ ; Preserve the original Spec, Init and Body spec: copy spec init: copy/deep init body: copy/deep body ; Collect static and local variables svars: set-words init lvars: set-words/deep body unless empty? svars [ ; create the static context and bind Init and Body to it use svars reduce [reduce [init body]] ] unless empty? lvars: exclude exclude lvars locals? spec svars [ ; declare local variables insert any [find spec /local insert tail spec /local] lvars ] do init make function! spec body ]
我们的 Rebol 函数模型将是一个 Rebol 对象 FUNCTION!-MODEL,它具有适当的属性。Rebol 函数的完全必要属性是 SPEC 和 BODY。
为了准确地模拟 Rebol 函数的当前行为,我们的 FUNCTION!-MODEL 需要 CONTEXT、CONTEXT-WORDS 和 RECURSION-LEVEL 属性来模拟 Rebol 函数在递归调用期间的行为
function!-model: make object! [ spec: none body: none context: none context-words: none recursion-level: none ]
该函数获取 SPEC 和 BODY 属性,创建一个新的 FUNCTION!-MODEL 并初始化它。
func-model: function [ {create a function!-model} spec [block!] body [block!] ] [result aw] [ result: make function!-model [] ; SPEC and BODY are deep copied result/spec: copy/deep spec result/body: copy/deep body result ]
解释器启动时调用栈为空。
call-stack-model: make block! []
我们的模拟从收集函数参数的值并检查它们的类型开始。
评估函数获取一个 FUNCTION!-MODEL 以及它应该存储到其局部上下文单词(即其所有参数、可选参数、细化和局部单词的值)的值块。
我们只模拟没有 THROW/CATCH 属性的函数最常见的情况。
我们模型的第一部分执行主体
exec: func [body] [do body]
模拟
evaluate-model: function [ {evaluate a function!-model} f-model {the evaluated function!-model} values [block!] {the supplied values} ] [old-values result] [ ; detect recursive call if (f-model/recursion-level: f-model/recursion-level + 1) > 1 [ ; push the old values of context words to the stack insert/only tail call-stack-model second f-model/context ] set/any f-model/context-words values ; execute the function body error? set/any 'result exec f-model/body ; restore the former values from the stack, if needed if (f-model/recursion-level: f-model/recursion-level - 1) > 0 [ ; pop the old values of the context words from the stack set/any f-model/context-words last call-stack-model remove back tail call-stack-model ] return get/any 'result ]
我们的模型只使用一个上下文来处理 FUNCTION!-MODEL 的整个生命周期,无需更改其 BODY 的绑定。我将这种行为称为动态递归补丁。
一些测试
probeblk: func [] [ prin mold blk prin ": " print mold reduce blk ] recfun: func-model [x] [ append blk 'x either x = 2 [ probeblk ] [ evaluate-model recfun [2] ] ] blk: copy [] evaluate-model recfun [1] ; [x x]: [2 2] probeblk ; [x x]: [1 1]
如果我们将模拟的行为与真实的 Rebol 函数进行比较,我们会得到
recfun: func [x] [ append blk 'x either x = 2 [ probeblk ] [ recfun 2 ] ] blk: copy [] recfun 1 ; [x x]: [2 2] probeblk ; [x x]: [1 1]
这表明模拟非常准确,并且 Rebol 函数也使用动态递归补丁。
虽然动态递归补丁可以在某些情况下加速评估,但它也有其缺点
f-returning-x: func [x] [ func [] [x] ] f-returning-ok: f-returning-x "OK" f-returning-ok ; == "OK" f-returning-bug: f-returning-x "BUG!" ; so far so good, but now: f-returning-ok ; == "BUG!"
正如我们上面所看到的,计算绑定有其优点,而动态递归补丁并不理想。结果促使我实现计算绑定函数,并将它们的行为与动态递归补丁函数的行为进行比较。
计算绑定函数将在每次被调用时创建一个新上下文,并相应地绑定它们的主体。我们甚至可以使用上述模拟的一部分来实现它们。
closure: func [ [catch] spec [block!] {Help string (opt) followed by arg words (and opt type and string)} body [block!] {The body block of the closure} /local spc item result ] [ spc: make block! 1 + (2 * length? spec) insert/only spc [throw] result: make block! 5 + length? spec insert result reduce [:do :make :function! spc body] parse spec [ any [ set item any-word! ( insert tail result to word! :item insert tail spc to get-word! :item insert/only tail spc [any-type!] ) | skip ] ] throw-on-error [make function! spec result] ]
第一次测试
recfun: closure [x] [ append blk 'x either x = 2 [ probeblk ] [ recfun 2 ] ] blk: copy [] recfun 1 ; [x x]: [1 2] probeblk ; [x x]: [1 2]
这肯定比以前好多了。第二次测试
f-returning-x: closure [x] [ func [] [x] ] f-returning-ok: f-returning-x "OK" f-returning-ok ; == "OK" f-returning-bug: f-returning-x "BUG!" ; so far so good, but now: f-returning-ok ; == "OK"
结束。