跳转到内容

Rebol 编程/高级/绑定学

来自 Wikibooks,开放世界中的开放书籍

作者: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

BIND 函数

[编辑 | 编辑源代码]

当我们需要获得一个词语,该词语具有给定 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

当 WORDS 参数是词语时绑定

[编辑 | 编辑源代码]

观察(有效绑定):如果可能,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 词语。

当 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
    ]
]

推论(词语相等):当且仅当以下条件之一成立时,两个词语相等

  1. 词语的拼写完全相同
  2. 词语是别名

推论(别名和 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 中只有单个词语与上下文相关联。

USE 函数

[编辑 | 编辑源代码]

为了尽可能精确,我们将用 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
]

具有 MAKE OBJECT!-like 处理局部单词的函数

[编辑 | 编辑源代码]

在我们尝试模拟函数评估之前,我们可以问一下我们是否可以使用与 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 函数模型将是一个 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
]

FUNC 函数模型

[编辑 | 编辑源代码]

该函数获取 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"

结束。

华夏公益教科书