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 資料型別。
結束。