Rebol 编程/高级/标识
作者:拉迪斯拉夫·梅奇尔
Rebol 是一种表达式导向语言。表达式成功求值将产生一个值。在成功执行两个表达式求值之后,我们可以合理地询问我们是否获得了两个值,或者仅仅是一个实际上是两个求值结果的值。
过去,这个问题成为争论的主题。邮件列表中的答案从
- "只有 Rebol 设计师才能回答这个问题。"(S 的观点)
到
- "两个表达式求值永远不会产生一个 Rebol 值。如果求值是可辨别的,那么它们的结果也应该可以辨别。"(N 的观点)
"S 的观点"可以被称为"怀疑论者",因为它表达了我们对找到这个问题的正确答案的能力的怀疑。为了回应"S 的观点",我们总结了 Rebol 标识的属性,并表明我们能够找到两个成功的表达式求值是否产生了一个 Rebol 值。我们通过"机械地"定义一个identical?函数来做到这一点,它正是这样做的。
仔细考察"N 的观点",人们可能会倾向于称之为"虚无主义者",因为它表明"两个表达式求值永远不会产生一个 Rebol 值"。接受这个想法的后果甚至比接受"S 的观点"更深远,"S 的观点"至少允许我们在有疑问时向 Rebol 设计师询问。与之相反,"N 的观点"可能会阻止我们思考 Rebol 值的标识,并阻止我们将其用于实际目的。我们对identical?函数的定义看起来足以反驳这个观点,表明 Rebol 标识是存在的,并且可以实际使用。
在我们寻找 Rebol 标识的过程中,我们将讨论其他(曾经有争议的)问题,例如"给定值是否可变?"或"给定值具有哪些属性?"并实现几个(希望有趣的)函数。
我要感谢乔尔·尼利、罗曼诺·保罗·滕卡以及其他许多在 Rebol 邮件列表中与我讨论过这个问题的人。
本文的版本是在 Rebol/View 2.7.6.3.1 中测试的。解释器的其他版本在一些细节上可能会有所不同。
想要检查所有示例的读者可以运行这段代码
do http://www.rebol.org/download-a-script.r?script-name=identity.r
,它定义了本文中的函数。
我们寻找目标的identical?函数必须接受两个表达式求值的任何结果或结果,并产生一个logic!类型的值。
我们把具有这些属性的函数称为关系。
众所周知,我们可以使用 Rebol 块或词来引用 Rebol 值。示例
a-char: #"a"
结果是a-char变量引用了我们期望的字符
a-char ; == #"a"
现在让我们求值这个表达式
a-block: head insert copy [] a-char
结果是现在即使上面块的第一个条目也引用了该字符。
为了使引用操作的行为一致,我们期望对于任何非空块,表达式
identical? first block first block
应该产生true,并且相同的逻辑要求导致我们期望对于任何变量var,表达式
identical? get/any var get/any var
也应该产生true。我们称这个属性为自反性。
让我们有一个块block,它有两个条目,使得表达式
identical? first block second block
产生true。知道这一点,我们可以得出结论,块的两个条目引用了一个值。如果确实如此,那么
identical? second block first block
也应该产生true。使用相同的推理,我们期望对于每个变量var1和var2,如果
identical? get/any var1 get/any var2
产生true,那么表达式
identical? get/any var2 get/any var1
也应该产生true。
我们称这个属性为对称性。
类似地,我们可以得出结论,identical?函数应该具有以下属性
如果block是一个具有三个条目的块,并且两者都
identical? first block second block
和
identical? second block third block
产生true,那么
identical? first block third block
也应该产生true。
如果var1、var2和var3是变量,并且两者都
identical? get/any var1 get/any var2
和
identical? get/any var2 get/any var3
产生true,那么
identical? get/any var1 get/any var3
也应该产生true。
我们称这个属性为传递性。
定义(等价):我们把任何具有上述描述的属性的 Rebol 函数(即自反、对称和传递的关系)称为等价。
根据我们之前的观察,我们重申 Rebol 标识必须是等价的。
让我们使用这个定义来找出某些函数是否为等价函数。例如,实现"N 的观点"的never函数
never: func [ a [any-type!] b [any-type!] ] [false]
是一个既对称又传递的关系。但是,它不自反,因此它不是等价的。
另一方面,always函数
always: func [ a [any-type!] b [any-type!] ] [true]
是一个等价函数,尽管它是一个平凡的等价函数。
观察:same?函数不是等价函数。
证明
- same?函数对于unset!和error!类型的值是未定义的,因此它不是一个关系。
- same?函数对于关闭的端口是未定义的,因此它不是一个关系。
block: reduce [make port! http://] same? first block first block ;** Access Error: Port none not open ;** Near: same? first block first block
- same?函数对于具有不同面值的money!类型的值是未定义的,因此它不是一个关系。
same? USD$1 EUR$1 ** Script Error: USD$1.00 not same denomination as EUR$1.00 ** Near: same? USD$1.00 EUR$1.00
- 当获得struct!类型的值作为参数时,same?函数不会返回一个logic!类型的值,因此它不是一个关系。
block: reduce [make struct! [] none] same? first block first block ; == none
- same?函数在比较decimal!类型的值时不具有传递性。
block: reduce [1.0 1.0 + 5e-16 1.0 + 1e-15] same? first block second block ; == true same? second block third block ; == true same? first block third block ; == false
- same?函数在比较date!类型的值时不具有传递性。
block: [5/Sep/2006/0:00 5/Sep/2006 5/Sep/2006/1:00] same? first block second block ; == true same? second block third block ; == true same? first block third block ; == false
以上任何一点都足以证明我们的观察结果证毕。
观察:解释器中没有内置等价函数。
实际上,解释器中没有内置关系函数。这可以通过检查每个内置函数来验证,就像我们对same?函数所做的那样。
术语:让我们有一个等价函数eq和一个具有两个条目的块block。如果表达式
eq first block second block
产生true,我们说
- "对于eq 等价性,block 块的第一个条目引用的值等同于block 块的第二个条目引用的值。"
, 或者
- "对于eq 等价性,block 块的第一个条目引用的值无法辨别于block 块的第二个条目引用的值。"
如果上述表达式结果为false,我们说
- "对于eq 等价性,block 块的第一个条目引用的值不等同于block 块的第二个条目引用的值。"
, 或者
- "对于eq 等价性,block 块的第一个条目引用的值可以辨别于block 块的第二个条目引用的值。"
精细度
[edit | edit source]为了能够比较等价性,我们使用
定义 (精细度): 我们说等价性eq1比eq2更精细或一样精细 (反之,eq2比eq1更粗糙或一样粗糙) 如果对于任何具有两个条目的block 块,其中
eq1 first block second block
结果为true,
eq2 first block second block
结果也为true。
定义 (相同精细度): 我们说等价性eq1比eq2一样精细,如果对于任何具有两个条目的block 块,其中
eq1 first block second block
结果为true 当且仅当
eq2 first block second block
结果为true。
观察: 上述定义的always 函数是最粗糙的等价性。
Rebol 标识的定义
[edit | edit source]等价性的概念没有完全定义标识,因为例如always 函数是一种等价性,但它不是我们正在寻找的标识。
为了定义标识,我们采用了一个被称为的逻辑原理
观察 (相同事物不可辨别): 任何值都无法与自身辨别。
作为该原理的推论,我们得到
观察 (莱布尼茨法则): identical? 函数必须是 Rebol 中最精细的等价性。
证明: 我们已经证明 identical? 必须是一种等价性。让我们有一个等价性 eq 和一个具有两个条目的 block 块,其中表达式
identical? first block second block
结果为true。如果 identical? 函数是 Rebol 标识,我们知道块的第一个和第二个条目引用了同一个 Rebol 值。根据相同事物不可辨别的原理,eq 等价性无法区分first block
和second block
,这证明了identical? 比eq 更精细或一样精细 q.e.d.
我们使用莱布尼茨法则定义
定义 (标识): 我们将一个等价性称为标识,如果它是 Rebol 中最精细的等价性。
观察: 任何两个标识必须具有相同的精细度。
证明: 如果我们有两个具有不同精细度的标识,其中一个就不会是最精细的 q.e.d.
观察: 上述定义的标识 (如果存在) 满足相同事物不可辨别的原理。
证明留给读者作为练习。
虽然我们成功地唯一定义了标识,但我们只完成了一半,因为我们还没有成功实现它。
为了能够实现标识,我们需要收集更多关于 Rebol 值的信息。
类型属性
[edit | edit source]让我们注意到,每个 Rebol 值都具有一个类型。可以按如下方式定义一个类型比较等价性
equal-type?: func [ {do the values have equal type?} a [any-type!] b [any-type!] ] [equal? type? get/any 'a type? get/any 'b]
观察: equal-type? 函数区分具有不同数据类型的值。
观察: same? 函数并不总是区分具有不同数据类型的值。
证明: 如果我们定义
block: [3 3.0]
那么
same? first block second block
结果为true,而
equal-type? first block second block
结果为false。
如果我们定义
block: [a a:]
那么
same? first block second block
结果为true,而
equal-type? first block second block
结果为false q.e.d.
换行属性
[edit | edit source]观察 (换行属性): 每个 Rebol 值都具有一个换行属性。
证明: 在类型属性的情况下,我们不需要证明每个 Rebol 值都具有一个类型,因为它被广泛记录,有许多具有各种类型的值的示例,并且有一个type? 本地函数可以返回任何给定值的类型。
为了证明换行属性的存在,我们需要对其进行记录,提供具有不同换行属性值的示例,并定义一个new-line-attribute? 函数,该函数返回任何给定值的换行属性。
我们从文档开始。换行属性是一个值,它可以是true 或false。它标记块中换行的位置。如果块条目引用一个具有设置为true 的换行属性的值,则mold 函数在格式化块时将插入一个换行符。
检查任何值的换行属性的new-line-attribute? 函数可以定义为
new-line-attribute?: func [ {returns the new-line attribute of a value} value [any-type!] ] [ new-line? head insert/only copy [] get/any 'value ]
不难观察到,new-line-attribute? 函数对于任何给定值只返回true 或false。
另一个称为new-line-attribute 的函数可以返回具有按需设置的换行属性的值
new-line-attribute: func [ {returns a value with the new-line attribute set as specified} value [any-type!] attribute [logic!] ] [ return first new-line head change/only [1] get/any 'value attribute ]
现在我们可以定义
one: 1 new-line-attribute? one ; == false one-nl: new-line-attribute 1 true new-line-attribute? one-nl ; == true
,即我们成功地证明了存在具有设置为false 的换行属性的值,以及具有设置为true 的属性的值。
现在我们证明格式化一个具有引用one-nl 值的条目的块会创建一个包含换行符的字符串
mold head insert copy [] one-nl ; == "[^/ 1^/]"
这完成了我们换行属性观察的证明 q.e.d.
对换行属性的了解以及我们上面定义的new-line-attribute? 函数可以帮助我们定义一个比较换行属性的函数
equal-new-line?: func [ {compares new-line attribute of the values} a [any-type!] b [any-type!] ] [ equal? new-line-attribute? get/any 'a new-line-attribute? get/any 'b ]
不难观察到,equal-new-line? 是一种区分具有不同换行属性的值的等价性。举例说明
equal-new-line? one one ; == true equal-new-line? one-nl one-nl ; == true equal-new-line? one one-nl ; == false
另一方面,同样不难观察到,same? 函数并不区分具有不同换行属性的值
same? one one-nl ; == true
不修改其参数的函数
[edit | edit source]在 Rebol 中,有些函数会修改其参数 (通常在文档中说明)。要写一个简单的定义来描述 "函数修改其参数" 这句话的意思并不容易,所以让我们从简单的例子开始
f1: func [a] [2]
当观察到类似的行为时
f1 1 ; == 2
,一个不熟悉 "函数修改其参数" 含义的读者可能会想说 "f1 将参数值 1 修改为返回的值 2"。情况并非如此,因为我们试图在这里描述的修改必须是不同性质的。
为了帮助我们进行演示,让我们使用apply 函数将给定函数应用于块中提交的参数 (该函数在 %identity.r 文件中定义)。
在上述f1 函数的情况下,我们可以使用apply 来获得
apply/only :f1 arguments: [1] ; == 2
注意: 我们使用apply 函数的/only
细化来确保函数以 "原样" 获取块中包含的参数。
如果我们检查调用后的arguments 块,我们会发现它看起来没有改变,这表明参数值没有被f1 修改
arguments ; == [1]
让我们看另一个例子
f2: function [i [integer!]] [i + 1] f2 1 ; == 2
现在,人们可能会想说f2 改变了参数值,但经过验证
apply/only :f2 arguments: [1] ; == 2 arguments ; == [1]
可以观察到,包含参数值的块保持不变。
另一个函数,对于它,读者可能不会立即知道该函数是否会修改其参数,是上面定义的new-line-attribute 函数。让我们检查一下
one-nl: apply/only :new-line-attribute arguments: [1 #[true]] new-line-attribute? one-nl ; == true new-line-attribute? first arguments ; == false
这表明,虽然该函数产生一个具有设置为true 的换行属性的值,但原始参数值保持不变,其换行属性设置为false。
修改其参数的函数
[edit | edit source]我们使用set 函数作为我们第一个修改其第一个 (word) 参数的函数示例。
a: [1] arguments: [a [2]] get first arguments ; == [1] apply/only :set arguments get first arguments ; == [2]
此示例表明,第一个参数 (变量a) 最初引用了一个块,而设置后,它现在引用了另一个块。
我们接下来的示例将检查change 函数的行为。
apply/only :change arguments: [[1] 2] first arguments ; == [2]
在本例中,我们看到change 函数也改变了其第一个参数。
注意: 函数也可以修改其参数以外的其他值 - 例如,定义一个不带参数的函数来修改某些东西并不难。
修改其值的表达式
[edit | edit source]在函数的情况下,我们说明了 "函数修改其参数" 这句话的含义。可能会出现一个问题,即表达式是否会修改其中的值。
我们可以通过定义一个适当的函数并检查函数的行为来将这种情况转换为函数情况。
示例: 让我们找出类似表达式
1 + 1
是否修改其中的值。为了找出答案,让我们定义一个函数如下
f: func [a b] [ do reduce [a '+ b] ]
并提供适当的值作为参数
apply/only :f arguments: [1 1] arguments ; == [1 1]
在本例中,我们的发现是该表达式实际上没有修改其中的值。
这是一个修改其中值的表达式的示例
block/1: 2
,正如我们通过检查
block: [1] g: func [block value] [block/1: value] arguments: reduce [block 2] ; == [[1] 2] apply/only :g arguments arguments ; == [[2] 2]
发现的那样。
tuple: 1.1.1 h: func [tuple value] [tuple/1: value] arguments: reduce [tuple 2] ; == [1.1.1 2] apply/only :h arguments arguments [1.1.1 2]
将上述情况与一个看起来相似的示例进行对比
,这表明在本例中,第一个参数保持未修改。
h2: func [variable value /local path expression] [ path: to set-path! reduce [variable 1] expression: reduce [path value] do expression ] tuple: 1.1.1 arguments: [tuple 2] get first arguments ; == 1.1.1 apply/only :h2 arguments get first arguments ; == 2.1.1
对不起?最后一个示例是一个读者可能感到困惑的情况。显然,最后一个表达式也改变了一些东西!唯一的问题是,我们没有正确地猜测是什么!所以,让我们再试一次,这次猜测该表达式修改了变量
定义(可变值):可以修改的值我们称之为可变值。
定义(不可变值):不能修改的值我们称之为不可变值。
观察:Rebol 变量是可变的(参见set 和tuple 例子)。
观察:Rebol 块是可变的(参见change 例子)。
虽然我们对修改函数领域的探索看起来像是我们探索之旅的岔路,但现在我们展示了变异可以用来辨别值。在下面的例子中,我们定义了两个位集
bs1: make bitset! #{00} bs2: make bitset! #{00} same? bs1 bs2 ; == true
因此,根据same? 函数,bs1 和bs2 是无法辨别的。让我们定义一个特殊的等价性,称为equal-mutation?,如下所示
equal-mutation?: func [ bs1 [any-type!] bs2 [any-type!] /local state1 state2 ] [ ; we concentrate on bitsets, ; so one of the criteria used is, ; whether the "bitsetness" of both values equals unless equal? bitset? get/any 'bs1 bitset? get/any 'bs2 [return false] ; to further concentrate on bitsets we consider non-bitsets equivalent unless bitset? get/any 'bs1 [return true] ; check whether both bitsets yield equal results ; when searching for #"^(00)" unless equal? state1: find bs1 #"^(00)" find bs2 #"^(00)" [return false] ; now the bitsets either both contain or don't contain #"^(00)" either state1 [ ; both bitsets contain #"^(00)", so let's remove it from bs1 remove/part bs1 "^(00)" ; we removed #"^(00)" from bs1, ; check, whether we find it in bs2 state2: find bs2 #"^(00)" ; reverse the mutation insert bs1 "^(00)" ] [ ; both bitsets don't contain #"^(00)", so let's insert it into bs1 insert bs1 "^(00)" ; we inserted #"^(00)" into bs1, ; check, whether we find it in bs2 state2: find bs2 #"^(00)" ; reverse the mutation remove/part bs1 "^(00)" ] ; bitsets are discernible, if STATE1 and STATE2 are equal state1 <> state2 ]
这种等价性可以辨别上面的bs1 和bs2 位集
equal-mutation? bs1 bs2 ; == false
我们已经收集了所有必要的信息。same? 函数接近我们的目标,因此我们将尝试尽可能地使用它,并注意它无法执行我们想要的操作的情况。
观察:Rebol 中最精细的等价性是
identical?: func [ {are the values identical?} a [any-type!] b [any-type!] /local statea stateb ] [ case [ ; compare types not-equal? type? get/any 'a type? get/any 'b [false] ; compare new-line attributes not-equal? new-line-attribute? get/any 'a new-line-attribute? get/any 'b [false] ; handle #[unset!] not value? 'a [true] ; errors can be disarmed and compared afterwards error? :a [same? disarm :a disarm :b] ; money with different denominations are discernible all [money? :a not-equal? first a first b] [false] ( ; for money with equal denominations it suffices to compare values if money? :a [a: second a b: second b] decimal? :a ) [ ; bitwise comparison is finer than same? and transitive for decimals statea: make struct! [a [decimal!]] none stateb: make struct! [b [decimal!]] none statea/a: a stateb/b: b equal? third statea third stateb ] ; this is finer than same? and transitive for dates date? :a [and~ a =? b a/time =? b/time] ; compare even the closed ports, do not ignore indices port? :a [ error? try [statea: index? :a] error? try [stateb: index? :b] return and~ statea = stateb ; ports with different indices are discernible equal? reduce [a] reduce [b] ] bitset? :a [ ; bitsets differing in #"^(00)" are discernible either not-equal? statea: find a #"^(00)" find b #"^(00)" [false] [ ; use the approach of the equal-mutation? equivalence either statea [ remove/part a "^(00)" stateb: find b #"^(00)" insert a "^(00)" ] [ insert a "^(00)" stateb: find b #"^(00)" remove/part a "^(00)" ] statea <> stateb ] ] ; for structs we compare third struct? :a [same? third a third b] true [:a =? :b] ] ]
index? 函数为我们提供了关于系列的有用信息。不幸的是,它有时无法按预期工作
a: "1" b: next a index? b ; == 2 clear a index? a ; == 1 index? b ; == 1 insert tail a #"1" index? a ; == 1 index? b ; == 2
此示例表明,虽然块b 的索引被打印为 1,但它实际上一直是 2!
我们可以定义一个能够生成“更稳定”值的函数
real-index?: func [ {return a realistic index for any series} series [series!] /local orig-tail result ] [ orig-tail: tail :series while [tail? :series] [insert tail :series #"1"] result: index? :series clear :orig-tail result ]
测试
a: "11" b: next a clear a index? b ; == 1 real-index? b ; == 2
虽然我们在上面使用same? 函数来获得identical? 的“有效”实现,但identical? 实际上并不依赖same? 函数。
证明:我们可以编写另一个(尽管效率较低)的identical? 实现,根本不使用same? 或=?
id2?: func [ {are the values identical?} a [any-type!] b [any-type!] /local statea stateb ] [ case [ ; compare types first not-equal? type? get/any 'a type? get/any 'b [false] ; compare new-line attributes not-equal? new-line-attribute? get/any 'a new-line-attribute? get/any 'b [false] ; handle #[unset!] not value? 'a [true] ; errors can be disarmed and compared afterwards error? :a [equal? disarm :a disarm :b] ; money with different denominations are discernible all [money? :a not-equal? first a first b] [false] ( ; for money with equal denominations it suffices to compare values if money? :a [a: second a b: second b] decimal? :a ) [ ; bitwise comparison is finer than same? and transitive for decimals statea: make struct! [a [decimal!]] none stateb: make struct! [b [decimal!]] none statea/a: a stateb/b: b equal? third statea third stateb ] ; this is finer than same? and transitive for dates date? :a [and~ a = b a/time = b/time] ; compare even the closed ports, do not ignore indices port? :a [ error? try [statea: index? :a] error? try [stateb: index? :b] return and~ statea = stateb ; ports with different indices are discernible equal? reduce [a] reduce [b] ] bitset? :a [ ; bitsets differing in #"^(00)" are discernible either not-equal? statea: find a #"^(00)" find b #"^(00)" [false] [ ; use the approach of the equal-mutation? equivalence either statea [ remove/part a "^(00)" stateb: find b #"^(00)" insert a "^(00)" ] [ insert a "^(00)" stateb: find b #"^(00)" remove/part a "^(00)" ] statea <> stateb ] ] ( ; for structs we compare third if struct? :a [a: third a b: third b] series? :a ) [ either equal? real-index? :a real-index? :b [ ; A and B have equal index, it is sufficient to compare tails a: tail :a b: tail :b ; use INSERT to mutate A insert a #"1" stateb: 1 = length? b ; undo the mutation clear a stateb ] [false] ] any-word? :a [ ; compare spelling either not-strict-equal? mold :a mold :b [false] [ ; compare binding equal? bind? :a bind? :b ] ] true [:a = :b] ] ]
讨论:对于一些读者来说,这个发现可能只是一种巧合。为了解释为什么它不是巧合,请考虑用户可能通过取消设置same? 函数来限制语言。我们证明了限制后的语言仍然会具有其身份,并且身份将是“功能齐全”的,因为它会反映用户可以访问的 Rebol 值的所有属性。如果添加same? 函数增加了任何其他属性,那么这种属性将毫无用处,因为它只能被same? 函数访问。
实现了 Rebol 身份后,让我们尝试定义其他有趣的概念和函数。
示例(错误和对象)
error? f: make error! "OK" error? g: f h: disarm f error? i: make error! "OK" ; now all the values look the same probe disarm f probe disarm g probe h probe disarm i h/arg1: "KO" probe disarm f probe disarm g probe h probe disarm i
在此示例中,更改影响了单词h 引用的对象以及f 和g 引用的错误,而它没有影响i 引用的错误值。我们可以说,g 和h 引用的值虽然不是同一个值,但从某种意义上说,它们是亲属。
类似的属性也可以在单词中观察到
set 'a 1 alias 'a "aa" set 'aa 2 get 'a ; == 2 same? 'a 'aa ; == false
让我们定义一个比identical? 更粗糙的等价性,对应于我们在前面示例中看到的属性。relatives? 等价性如果其第一个参数的每个更改都是其第二个参数的更改,反之亦然,如果其第二个参数的每个更改都是其第一个参数的更改,则会产生true。
relatives?: func [ { Two values are relatives, if every change of one affects the other too } a [any-type!] b [any-type!] /local var var2 ] [ ; errors are relatives with objects if error? get/any 'a [a: disarm :a] if error? get/any 'b [b: disarm :b] ; ports are relatives with contexts if port? get/any 'a [a: bind? in :a 'self] if port? get/any 'b [b: bind? in :b 'self] ; objects if not-equal? object? get/any 'a object? get/any 'b [return false] if object? get/any 'a [ ; objects are relatives with contexts a: bind? in :a first first :a b: bind? in :b first first :b return same? :a :b ] ; structs if not-equal? struct? get/any 'a struct? get/any 'b [return false] if struct? get/any 'a [return same? third :a third :b] ; series if not-equal? series? get/any 'a series? get/any 'b [return false] if series? get/any 'a [ if not-equal? list? :a list? :b [return false] ; series with different indices can be relatives a: tail :a b: tail :b unless list? :a [ ; any-blocks are relatives with blocks ; any-strings are relatives with strings parse :a [a:] parse :b [b:] ] return same? :a :b ] ; variables if not-equal? all [ any-word? get/any 'a bind? :a ; is it a variable? ] all [ any-word? get/any 'b bind? :b ; is it a variable? ] [return false] if all [any-word? get/any 'a bind? :a] [ return found? all [ equal? :a :b same? bind? :a bind? :b ] ] ; functions if not-equal? any-function? get/any 'a any-function? get/any 'b [ return false ] if any-function? get/any 'a [return same? :a :b] ; bitsets if not-equal? bitset? get/any 'a bitset? get/any 'b [return false] if bitset? get/any 'a [ unless equal? var: find a #"^(00)" find b #"^(00)" [return false] either var [ remove/part a "^(00)" var2: find b #"^(00)" insert a "^(00)" ] [ insert a "^(00)" var2: find b #"^(00)" remove/part a "^(00)" ] return var <> var2 ] ; all other values true ]
用法
a: [1] insert a reduce [a] b: [1] insert b reduce [b] relatives? a a/1 ; == true relatives? b b/1 ; == true relatives? a b ; == false a: [1] b: tail a remove a relatives? b b ; == true a: "11" b: next a relatives? a b ; == true index? a ; == 1 index? b ; == 2
在展示set 函数修改其第一个参数的示例中,我们使变量a' 指向另一个块。不同的表述可能如下:“我们更改了变量a' 的引用。”
让我们看看行为的另一个例子
alias 'a "ax" a: [1] b: a a ; == [1] b ; == [1] a: [2] a ; == [2] ax ; == [2] b ; == [1]
与之前类似,我们可以说我们没有更改块,但在这种情况下,看起来我们更改了a' 以及ax' 的引用。实际上,a 和ax 是别名,使用一个公共引用,这意味着a' 的引用和ax' 的引用只是同一个引用的不同名称。
换句话说,我们也可以谈论引用的身份。来自 [Contexts] 的same-variable? 函数检查其参数单词的引用的身份。
行为的另一个例子
c: [] insert/only tail c [1] insert/only tail c first c d: next c first c ; == [1] second c ; == [1] first d ; == [1] poke c 2 [2] first c ; == [1] second c ; == [2] first d ; == [2]
我们的问题:“我们是否更改了second c
引用的块?”答案是否定的,与上面类似,因为second c
引用的块也被first c
引用,并且我们没有看到first c
的任何变化。结论:“我们只更改了second c
的引用。”此外,我们看到second c
和first d
只是一个引用的不同名称。
以下是一个检查两个系列引用的身份的函数。该函数使用 PICK 函数的索引约定,虽然不太复杂的索引会带来更简单的实现
same-series-references?: func [ { Find out, whether the INDEX1 reference in the SERIES1 is the same as the INDEX2 reference in the SERIES2 } series1 [series!] index1 [integer!] series2 [series!] index2 [integer!] ] [ if zero? index1 [return zero? index2] if zero? index2 [return false] index1: either negative? index1 [index1] [index1 - 1] index2: either negative? index2 [index2] [index2 - 1] found? all [ relatives? :series1 :series2 equal? (real-index? :series1) + index1 (real-index? :series2) + index2 ] ]
让我们检查我们之前的发现
same-series-references? c 2 d 1 ; == true
不幸的是,这并不是全部真相。虽然我们的实现和发现是正确的,但某些访问方法在某些状态下并不适用于系列。这就是为什么我们得到
clear c same-series-references? c 2 d 1 ; == true pick c 2 ; == none pick d 1 ** Script Error: Out of range or past end ** Near: pick d 1
这看起来就像引用并不相同,尽管差异是由pick 造成的,它在检查d 后没有使用引用。
细心的读者可能会注意到我们忽略了位集引用的身份,但这种情况相当简单,因此可以留给读者作为练习。
拥有一个能够找到给定值的引用的函数可能会有用
find-reference: func [ {find a reference to a given value in a series} series [series!] value [any-type!] ] [ while [not tail? :series] [ if identical? first :series get/any 'value [ return :series ] series: next :series ] none ]
另一个此类函数可能是能够搜索给定值的亲属的函数
find-relative: func [ {find a reference to a relative of a value in a given series} series [series!] value [any-type!] ] [ while [not tail? :series] [ if relatives? first :series get/any 'value [ return :series ] series: next :series ] none ]
find-reference 函数的应用,一个能够递归地找出块或其子块是否包含具有给定属性的值的函数。该函数即使对于循环块也能正常工作
rfind: function [ { find out whether a block or its subblocks contain a value with a given property } block [block!] property [any-function!] ] [rf explored] [ explored: make block! 0 rf: function [ block ] [result] [ if not find-reference explored block [ insert/only tail explored block while [not tail? block] [ either (property first block) [ return block ] [ if all [ block? first block result: rf first block ] [return result] ] block: next block ] ] none ] rf block ]
即使两个值不相同,它们也可能处于相同状态。让我们定义另一个能够找出两个 Rebol 值是否处于相同状态的等价性。因为定义的函数将是一个等价性,所以它将不会具有equal?、strict-equal? 等函数的主要缺点。
存在一个关于复杂值的难题。如果两个函数可能产生不同的结果,它们不应该被认定为相等。不可能定义一个通用的比较算法,能够在两个复杂函数不完全相同的情况下判断它们是否相等。因此,我对于函数采用了一种简单的做法(判断值是否完全相同)。这种方法也适用于端口。
下面定义的 **equal-state?** 函数将测试所有可用的值属性,而不进行修改。对于某些应用,更粗略的等价性可能更合适。
一个辅助函数
find-pair: func [ {find a pair of occurrences in a given series} series [series!] a [any-type!] b [any-type!] ] [ while [not tail? :series] [ if all [ identical? first first :series get/any 'a identical? second first :series get/any 'b ] [return :series] series: next :series ] none ] equal-state?: function [ {are the values in equal state?} a [any-type!] b [any-type!] ] [compo compb compw rc] [ compo: make block! 0 compb: make block! 0 compw: make block! 0 rc: function [ a [any-type!] b [any-type!] ] [i1 i2] [ unless equal-type? get/any 'a get/any 'b [return false] unless equal-new-line? get/any 'a get/any 'b [return false] if identical? get/any 'a get/any 'b [return true] if error? :a [ a: disarm :a b: disarm :b ] if object? :a [ if find-pair compo :a :b [return true] insert/only tail compo reduce [:a :b] return rc bind first a in a 'self bind first b in b 'self ] if any-word? :a [ if strict-not-equal? mold :a mold :b [return false] if find-pair compw :a :b [return true] insert/only tail compw reduce [:a :b] return rc get/any :a get/any :b ] if struct? :a [ return found? all [ equal? first :a first :b equal? second :a second :b equal? third :a third :b ] ] if series? :a [ error? try [i1: index? :a] error? try [i2: index? :b] if not-equal? i1 i2 [return false] a: head :a b: head :b if not-equal? length? :a length? :b [return false] if any-string? :a [return strict-equal? :a :b] if find-pair compb :a :b [return true] insert/only tail compb reduce [:a :b] repeat i length? :a [ unless rc pick :a i pick :b i [return false] ] return true ] false ] rc get/any 'a get/any 'b ]
**观察结果**(另一个对 **identical?** 的非正式描述):两个 Rebol 表达式计算产生一个值,如果第一个表达式计算结果的状态等于第二个表达式计算结果的状态,并且第一个表达式计算结果的每一次修改都是第二个表达式计算结果的修改。
**推论**:两个 Rebol 表达式计算产生一个值,如果结果是不可变的,并且第一个表达式计算结果的状态等于第二个表达式计算结果的状态。
我们可以使用很多不同的块循环性的定义。第一个定义可能是最严格的
strict-cyclic?: function [ block [any-block!] ] [rec in] [ in: make block! 1 rec: func [checked] [ if not positive? real-length? :checked [ return false ] if find-reference in :checked [ return true ] insert/only tail in :checked foreach value :checked [ if all [ any-block? get/any 'value rec :value ] [return true] ] remove back tail in false ] rec :block ]
这里还有另一个定义,它产生的结果与原生函数的结果更接近
native-cyclic?: function [ block [any-block!] ] [rec in] [ in: make block! 1 rec: func [checked] [ if not positive? real-length? :checked [ return false ] if find-relative in :checked [ return true ] insert/only tail in :checked foreach value :checked [ if all [ any-block? get/any 'value rec :value ] [return true] ] remove back tail in false ] rec :block ]
deepcopy: function [ block [any-block!] ] [rc copied copies] [ copied: make block! 0 copies: make block! 0 rc: function [ block ] [result found] [ either found: find-reference :copied :block [ return pick copies index? found ] [ result: make :block :block insert/only tail copied :block insert/only tail copies :result while [not tail? :result] [ if any-block? first :result [ change/only :result rc first :result ] result: next :result ] head :result ] ] rc :block ]
Rebol 值的属性是与它们关联的值。Rebol 值的状态是其属性的“组合”。如上所述,每个 Rebol 值都具有以下通用属性
- 类型属性,
- 以及换行符属性
对于 **none!** 和 **unset!** 类型的值,以上是它们的唯一属性,即 **none!** 和 **unset!** 类型的值没有任何类型特定的属性。
当一个 Rebol 值发生改变时,至少有一个属性发生改变。我们称可以改变的 Rebol 值属性为 *易变属性*,而不能改变的属性为 *常规属性*。
**观察结果**(属性和可变性):如果一个值至少有一个易变属性,它就是可变的。
**观察结果**:类型和换行符都是常规属性。
其他类型还具有下面列出的附加类型特定属性。
类型特定属性
字符代码
字符是不可变的。
类型特定属性
真/假
逻辑值是不可变的。
类型特定属性
数据类型代码
数据类型是不可变的。
类型特定属性
数字代码(32 位有符号,二进制补码,一进制补码或带符号的幅度)
整数是不可变的。
类型特定属性
数字代码(64 位 IEEE 754 二进制浮点数)
小数是不可变的。
类型特定属性
货币代码(三个字母),数字代码(64 位 IEEE 754 二进制浮点数)
货币是不可变的。
类型特定属性
x 坐标(第一个坐标,32 位有符号整数),y 坐标(第二个坐标,32 位有符号整数)
对是不可变的。
类型特定属性
年,月,日,时间,时区
日期是不可变的。
类型特定属性
时,分,秒
时间值是不可变的。
类型特定属性
长度,第一个字节,第二个字节,第三个字节,...,第十个字节
元组是不可变的。
类型特定属性
面,事件类型,偏移量,键,时间,控制,移位,双击
事件可能是不可变的。
类型特定属性(有关词的更多信息,请参阅 绑定学)
拼写
未绑定词是不可变的。
类型特定属性(有关词的更多信息,请参阅 绑定学)
拼写,绑定,值引用
值引用属性是易变的;Rebol 变量在其生命周期内可以引用不同的值。
类型特定属性
索引,长度,元素引用
长度属性是易变的(可以通过 **insert** 函数改变),元素引用也是易变的(可以通过 **change** 函数改变)。
与列表不同,任何字符串和任何块的索引属性都是常规属性,在列表中它是易变的。
序列的子类型为
- any-string!
- any-block!
- list!
any-string! 数据类型由以下组成:binary!, email!, file!, issue!, string!, tag! 和 url!
any-block! 数据类型由以下组成:block!, get-path!, lit-path!, paren!, path! 和 set-path!
图像就像序列,区别在于它们可以使用对进行索引。
哈希本质上是使用哈希表进行查找的块。
类型特定属性为
长度,元素引用
元素引用是易变的。(**insert** 和 **remove** 函数会改变元素引用。)
类型特定属性为
规范,二进制代码
二进制代码属性是易变的。
类型特定属性
路径,状态
路径和状态属性都是易变的。
类型特定属性
变量集
除了全局上下文,对象中的变量集不能被扩大。全局上下文可以使用make、to和load函数来扩大。
单个变量是易变的(见上文)。
Rebol 错误与对象相关。它们可以使用disarm函数转换为对象。
类型特定属性
索引,...
端口与对象相关(参见上面的relatives? 函数)。它们是可变的。
类型特定属性
规范,实现
规范属性是易变的。
类型特定属性
规范,引用
规范属性是易变的。
类型特定属性
规范,主体,函数上下文(类似于对象)
规范和主体属性是易变的,函数上下文中的单个变量也是易变的。
类型特定属性
数据类型集
类型集看起来是不可变的。
符号看起来更像是从实现中泄露出来的数据类型,而不是真正的 Rebol 数据类型。
结束。