跳轉到內容

Rebol 程式設計/Rebol 程式設計

來自華夏公益教科書

Rebol 程式設計

[編輯 | 編輯原始碼]

函式式和符號式程式設計

[編輯 | 編輯原始碼]

Rebol 的強大功能很大程度上來自它既是一種函數語言程式設計語言,也是一種符號式語言。

作為一種函式式程式語言,Rebol 使用一系列表示式(由函式構建),這些表示式被求值以產生結果流,這些結果流從一個表示式傳遞到另一個表示式。Rebol 沒有關鍵字,單詞根據上下文求值。一般來說,求值的順序是從左到右,特殊運算子除外。

一種符號式語言是允許你像語言中的任何其他值一樣表示和運算子號(單詞)的語言。隨著你對 Rebol 技能的提高,符號式程式設計的優勢將更加明顯。它是開啟方言之門的關鍵,它允許你使用更少的程式碼建立更強大的程式,以及將 Rebol 表示式傳送到網際網路上,以便在其他計算機系統上求值,而不僅僅是在你自己的系統上。(稱為“分散式計算”。)

分隔符

[編輯 | 編輯原始碼]

空白字元,如空格製表符換行符換頁符,就像在英語中一樣充當分隔符,將值彼此隔開。它們表示一個值的結束和另一個值的開始。

例如,這是一個包含三個值(整數1、運算子+和整數2)的序列,它們代表一個表示式

>> 1 + 2

請注意空格。如果我們省略空格,我們將獲得

>> 1+2

這被認為是一個語法上不合法的值。

其他可以扮演分隔符角色的字元是:( ) " [ ] { } ;

這一點很重要,因為在編寫 Rebol 程式碼時,語言允許你自由選擇,這樣你的程式就可以完全在一行很長的程式碼中編寫,或者在多行程式碼中進行分割和縮排。後一種方法是首選方法,建議讀者遵循有關如何格式化原始碼的已釋出指南。 (Rebol/Core 使用者指南 - 第 5 章 - 樣式指南)

運算子和簡單表示式

[編輯 | 編輯原始碼]

Rebol 使用算術運算子:+-*///**、布林運算子:andorxor 和比較運算子:<<=<>====?>>=。它們通常是二元的,在每側使用一個引數。例如

>> 2 > 3
== false

每個運算子都有一個函式對應物

運算子 函式
+ add
- subtract
* multiply
/ divide
// remainder
** power
and and~
or or~
xor xor~
< lesser?
<= lesser-or-equal?
<> not-equal?
= equal?
== strict-equal?
=? same?
> greater?
>= greater-or-equal?

例如,上面的表示式可以改寫如下

>> greater? 2 3
== false

例外

  1. 運算子可以用作字首(雖然這通常不建議,因為它可能會損害程式碼的可讀性,而且,它沒有得到官方支援)。
  2. - 運算子可以用作一元運算子(函式對應物稱為negate)。

示例

>> + 2 3
== 5
>> - 2
== -2

複雜運算子表示式

[編輯 | 編輯原始碼]

可以編寫更復雜的運算子表示式。有兩條規則需要牢記

  1. 所有運算子具有相同的優先順序。
  2. 運算子表示式從左到右求值。

示例

>> 1 + 2 * 3
== 9

請注意,左側的加法在乘法之前執行。如果我們想先執行乘法,我們可以重新排序表示式

>> 3 * 2 + 1
== 7

或者使用括號(對於長公式始終使用括號,可以避免錯誤)

>> 1 + (2 * 3)
== 7

假設我們想比較兩個表示式的結果:1 + 3 和 2 + 2。我們應該使用括號來達到預期的求值順序

>> (1 + 3) = (2 + 2)
== true

雖然第一對括號不是必需的(最左側的加法會優先執行),但這對於最右側的加法並不成立。如果我們省略第二對括號,我們將比較最左側的加法結果與 2,這是合法的,但我們將嘗試將 2 新增到false,這將導致非法加法

>> 1 + 3 = 2 + 2
** Script Error: Expected one of: logic! - not: integer!
** Near: 1 + 3 = 2

錯誤報告看起來不太容易理解,它與我們在以下情況下獲得的報告相同

>> false + 2
** Script Error: Expected one of: logic! - not: integer!
** Near: false + 2

收集表示式結果

[編輯 | 編輯原始碼]

通常我們只求值一個表示式,只需要一個結果。但是,可以一個接一個地求值多個表示式

>> 4 + 6 7 + 8 
== 15

它求值了 4 + 6,然後是 7 + 8,並且只顯示了最後一個。可以使用括號編寫類似的示例

>> (4 + 6) (7 + 8)
== 15

Rebol 只顯示最後一個表示式,但會跟蹤所有表示式。

>> do [a: 4 + 6 7 + 8]
== 15
>> a
== 10

它與以下內容相同

>> do [
 a: 4 + 6
 7 + 8
 ]
== 15

要從兩個或多個表示式中獲取所有結果,可以使用reduce 函式,如下所示

>> reduce [4 + 6 7 + 8]
== [10 15]

我們獲得了一個包含所有收集結果的塊。

另一個可能需要收集一些結果的重要情況可能是函式的求值,該函式需要一些引數。以add 函式為例

>> add 2 + 3 4 + 6
== 15

解釋:在這種情況下,我們要求直譯器求值add 函式。直譯器需要為該函式收集兩個引數,因此它對右側的兩個表示式進行求值,並獲得兩個結果,可以用作引數。它與以下內容相同

>> add (2 + 3) (4 + 6)
== 15


上面的描述對於具有更多或更少引數的函式有效,即對於僅接受一個引數的函式也是如此

>> abs -4 + -5
== 9

這裡直譯器需要求值abs 函式。因此它對右側的第一個表示式求值,並獲得 -9,它用作abs 函式的引數。它與以下內容相同

>> abs (-4 + -5)
== 9

因此,使用括號以避免錯誤...

由於直譯器收集函式引數的方式,看起來右側的運算子表示式優先於函式求值。

當函式使用未求值(或獲取)的引數時,很容易解釋為什麼情況並非如此:在這種情況下,直譯器不需要求值表示式來獲得引數的值。

在控制檯中進行簡單的程式設計

[編輯 | 編輯原始碼]

控制檯是您編寫小段程式碼以快速測試您的想法和函式的主要位置。無需編譯,表示式會在您按下Enter鍵時立即計算。這允許在控制檯中進行互動式程式設計,並有助於除錯大型程式。沒有編譯階段允許快速原型設計和測試。

我們之前看到了一個數學示例,但現在讓我們嘗試一些更復雜的內容

>> join "Hi" "There"
== "HiThere"

join 函式將兩個字串合併並返回一個新字串:"HiThere"。

現在讓我們透過在join前面插入reverse來反轉該字串中的所有字元

>> reverse join "Hi" "There"
== "erehTiH"

我們可以再次反轉它

>> reverse reverse join "Hi" "There"
== "HiThere"

現在您基本上觀察到 Rebol 語言的工作方式

您可以直接操作返回的值,因為它們輸出到函式左側。值從程式的右側流向左側,您可以在途中操作它們。

這是一個非常重要的觀察!可以無限地執行此類操作。這基本上是在 Rebol 中構建程式的方式。

>> reverse print join "Hi" "There"
HiThere
** Script Error: reverse expected value argument of type: series tuple pair
** Near: reverse print join "Hi" "There"

發生的事情是print 函式沒有返回reverse 函式可以使用的值。這就是為什麼您會在控制檯中看到print 的輸出和reverse 的錯誤輸出的原因。

此規則的另一個例外是二元運算子。它們通常在兩側消耗一個引數。

外框僅用於說明目的,但這就是您應該看到的操作方式。

本質上

函式返回值
您可以將其他運算子或函式用於它們的左側。
運算子返回值
類似於函式,但消耗(通常)兩側的引數。您可以將函式用於運算子表示式的左側或其他運算子(通常用於右側)以消耗其輸出。

混合函式和運算子的示例

等效程式碼是

>> print divide 2 + 2 8
0.5

運算子* 使用運算子+ 的輸出的示例

>> 2 + 3 * 4
== 20

記住值:變數

[編輯 | 編輯原始碼]

如上所述,Rebol 詞彙可以作為符號使用。除此之外,我們可以讓詞彙“工作”作為變數,即引用其他 Rebol 值。

讓我們嘗試透過構建一個多行簿記程式來做到這一點。我們想要使用一個名為wallet的變數。

首先,讓我們看看在控制檯中輸入它會發生什麼

>> wallet
** Script Error: wallet has no value
** Near: wallet

發生這種情況是因為該詞沒有分配值。我們需要為它分配一個值

>> wallet: $25

我的錢包裡有 25 美元。我可以簡單地透過輸入來返回此值

>> wallet
== $25.00

現在我想加 5 美元

>> wallet: wallet + $5
== $30.00

另一種方法是寫

>> wallet: add wallet 5
== $35.00

一直這樣寫會很乏味,所以我們想建立一個函式來處理向錢包中新增資金的任務。函式只是一段程式程式碼,每次您輸入特定詞語時都會執行該程式碼。一個簡單的函式是這樣建立的

>> earn: does [wallet: add wallet $5]

它與上面顯示的程式碼相同,但包含在方括號中,稱為,提供給does 函式,這意味著每次評估它時建立一個執行此程式碼塊的函式。它需要儲存,並且儲存方式與儲存數字相同。我們將新函式分配給詞語earn

這是 Rebol 的一大優勢之一,即儲存值和函式的工作方式相同!我們仍在遵循我們的從右到左規則。

這開闢了一些非常巧妙的可能性,我們將在後面的章節中探討。

我們的earn 函式已經建立。所以每次您輸入

>> earn
$40.00

>> earn
$45.00

這樣輸入容易多了,對吧?但是,如果您每次想賺取不同的金額怎麼辦?就像我們之前在reversejoin中看到的那樣,這些函式接受一個或多個引數。因此,我們需要使用func 而不是does

>> earn: func [amount] [wallet: add wallet amount]

請注意,我們的程式程式碼之前使用了另一個塊。這是我們儲存函式引數的地方,也稱為引數列表。數字5 已在程式碼中被amount 替換,它是引數列表中的一個變數。您可以從引數列表中獲取任意數量的引數,並在函式程式碼中使用任意次數。

我們可以同樣建立一個spend 函式

>> spend: func [amount] [wallet: subtract wallet amount]

現在您可以像使用任何其他函式一樣使用它們

>> earn $10
== $45.00

>> spend $25
== $20.00

您是否注意到我們在earnspend 的返回值中實際上使用了真正的 $ 符號?使用它,Rebol 將該數字識別為貨幣金額!

為什麼 Rebol 那樣進行賦值

[編輯 | 編輯原始碼]

您現在可能想知道,為什麼 Rebol 使用上述語法進行變數賦值?畢竟,大多數語言都使用這樣的等號

number = 10

為什麼 Rebol 那樣寫

number: 10

事實證明,Rebol並非僅僅為了不同而這樣做,它是語言的重要組成部分。

正如我們之前暗示的那樣,Rebol 的核心是一種高階語言。它超越了大多數其他語言,因為 Rebol 將程式碼、資料和元資料(描述其他資料的​​資料)的概念整合到一種語言中。(我們將在介紹“方言”時討論這一點。)

但現在,這樣想。當你寫

number = 10

你正在宣告

variable assignment-operator value

但是,當你寫

number: 10

你正在宣告

variable-defined-as value

這個事實使 Rebol 的變數定義脫穎而出,成為語言的特殊資料型別。定義是唯一的,不依賴於運算子(= 符號)的含義。這是一個強大的概念,在處理高階程式碼即資料和元資料時非常有用。

如果您仍然難以理解這種符號,那麼這樣想:在書面人類語言中,哪種更常見?事實上,如果您檢視電子郵件標題或 http 標題,值欄位是如何表達的?Rebol 方式。為什麼呢?

多行塊

[編輯 | 編輯原始碼]

控制檯允許您在單個表示式中使用多行程式碼。如果您以[ 開始一個塊並按下Enter 鍵,控制檯將不會停止接受輸入,直到您給出相應的]

為了讓您知道控制檯現在正在接受多行輸入,提示將從>> 更改為當前使用的分隔符,例如[,並且它將一直保持這種狀態,直到] 出現。

>> todays-earnings: [
[    $25.00
[    $30.00
[    $14.00
[    $10.00
[    ]

多行字串也是可能的,但請注意,多行字串使用{ } 而不是" "

>> very-long-string: {
{    This string
{    contains many
{    lines.
{    }

在控制檯中貼上程式碼

[編輯 | 編輯原始碼]

有時,您可能想在控制檯中嘗試程式碼示例以測試某個函式。只需從原始碼中複製它並貼上它(在 Windows 中使用Ctrl+CCtrl+V)。

常見陷阱

[編輯 | 編輯原始碼]

文字系列

[編輯 | 編輯原始碼]

學習 Rebol 的所有人都會犯的最常見的錯誤是處理函式中的文字系列。

例如,這裡有一個函式,它根據名稱作為引數列印一個“Dear”。

dear: func [
    name [string!]
    /local salute
][
    salute: "Dear "
    print append salute name
]

當函式第一次被呼叫時,結果如預期

>> dear "John"
Dear John

但是,當再次執行時,結果出乎意料

>> dear "Jane"
Dear JohnJane

發生這種情況是因為salute 變數初始化為文字字串“Dear”,該字串保留在函式體中,併成為更改的物件。append 函式透過將實際引數字串新增到該字串來更改該字串。

您可以透過檢查dear 函式在首次計算之前的結果來驗證這一點

probe :dear

這將導致

func [
    name [string!]
    /local salute
] [
    salute: "Dear "
    print append salute name
]

然後,當您在用引數“John”計算它後檢查它時

func [
    name [string!]
    /local salute
] [
    salute: "Dear John"
    print append salute name
]

現在很明顯,該字串已更改為“Dear John”。

為了確保salute 變數每次都正確初始化,我們需要在函式體中保持字串“Dear”不變。為了保護函式體中的字串,我們可以將它的一個副本分配給salute 變數,如下所示

Dear: func [
    name [string!]
    /local salute
] [
    salute: copy "Dear "
    print append salute name
]

這保證了對salute 字串的更改不會改變函式體中的原始字串。

使用塊時也會出現同樣的陷阱

>> test: func [l [integer!]] [b: [] repeat x l [append b x]]
>> test 2
== [1 2]
>> test 3
== [1 2 1 2 3]

為了確保test 函式不會改變它包含的塊,我們可以使用

b: copy []

在本例中,在test 函式的函式體中。

優先順序

[編輯 | 編輯原始碼]

Rebol 從左到右計算,但中綴運算子優先於函式,破壞了正常的計算流程。

當比較值時,會發生一個常見的錯誤

if length? series < 10 [print "less then 10"]
** Script Error: length? expected series argument of type: series port tuple bitset struct
** Near: if length? series < 10

這裡,中綴運算子< 優先於length? 函式。然後將結果(一個布林值)傳遞給函式length?,而實際上length? 期望的是一個系列引數而不是一個布林值。

為了解決這個問題,可以將表示式改寫為

if (length? series) < 10 [print "less than 10"]

或者作為

if 10 > length? series [print "less than 10"]

在後一種版本中,中綴運算子優先,並計算10,然後計算length?。由於length? 需要一個引數,因此它消耗series 並將值返回給> 以給出正確的計算結果。

缺少引數

[編輯 | 編輯原始碼]

另一個常見的錯誤,無論初學者還是專家都可能犯,就是忘記為函式提供引數。

在下面的例子中,我們忘記為append函式提供最後一個引數

>> append "example"
** Script Error: append is missing its value argument
** Near: append "example"

錯誤資訊中的“value”引數指的是什麼?使用help命令來找出答案。

>> help append
USAGE:
   APPEND series value /only
DESCRIPTION:
    Appends a value to the tail of a series and returns the series head.
    APPEND is a function value.
ARGUMENTS:
    series -- (Type: series port)
    value -- (Type: any)
REFINEMENTS:
    /only -- Appends a block value as a block

在這裡你可以看到,“value”是第二個引數。這就是缺少的部分。

額外的引數

[edit | edit source]

另一個常見的引數錯誤是提供太多引數。這種錯誤比較微妙,因為你不會得到錯誤資訊。

以下是一個例子。假設你有下面的表示式

if num > 10 [print "greater"]

但是,你決定在else情況下列印“not greater”。你可能會傾向於寫

if num > 10 [print "greater"] [print "not greater"]

這是一個錯誤,但是當你嘗試時,你不會得到錯誤資訊。第二個程式碼塊會被忽略。這是因為if函式只接受一個程式碼塊。如果你想要兩個程式碼塊,你應該使用either函式,如下所示

either num > 10 [print "greater"] [print "not greater"]

注意程式碼中的這些錯誤型別。

注意: Rebol 允許這些“額外”值而不產生錯誤是有充分理由的。它實際上是 Rebol 的一項特殊功能。考慮上面提到的reduce函式。允許多個表示式依次出現,可以讓你建立特殊的資料結果,這些結果非常有用。這是一個高階主題,但是看一下下面的例子,瞭解它在 Rebol 中的重要性。

>> reduce [if num > 10 [123] ["example" 456]]
== [123 ["example" 456]]

錯誤的引數型別

[edit | edit source]

如果為函式提供了不正確的引數型別,也會發生這種情況。在某些特定情況下,錯誤資訊可能有點令人困惑。

修改上面的if示例,如果你寫

if num > 10 print "greater"

你會得到這個錯誤

** Script Error: if expected then-block argument of type: block
** Near: if num > 10 print

之所以會發生這種情況,是因為你沒有為函式提供正確的引數型別。你想要寫的應該是

if num > 10 [print "greater"]

同樣,如果你想知道 Rebol 中“then-block”的含義,請使用help函式

>> help if
USAGE:
   IF condition then-block /else else-block
DESCRIPTION:
    If condition is TRUE, evaluates the block.
    IF is a native value.
ARGUMENTS:
    condition -- (Type: any)
    then-block -- (Type: block)
REFINEMENTS:
    /else -- If not true, evaluate this block
        else-block -- (Type: block)


Copy vs copy/deep

[edit | edit source]

給定以下序列

a: [a b c [d e f]]

執行copy

b: copy a
probe b
>> [a b c [d e f]]

現在b包含單詞a, b, c,它的第四個元素與a的第四個元素相同。可以透過追加到a/4並再次檢查b來檢視這一點

append a/4 'g
probe b
>> [a b c [d e f g]]

copy/deep上,c/4變為a/4的獨立副本,因此子塊a/4c/4並不相同。

c: copy/deep a
append a/4 'h
probe c
>> [a b c [d e f g]]

c/4不包含h,但b/4包含。

probe b
>> [a b c [d e f g h]]

bc都不會包含i,因為它在外部序列中。

append a 'i
probe c
>> [a b c [d e f g]]
probe b
>> [a b c [d e f g h]]

程式碼可讀性

[edit | edit source]

即使在控制檯中,你也可以生成相當長的函式和變數序列。如果你把它們全部放在一行中,可能很難閱讀,尤其是在 Rebol 使得使用括號進行評估變得不必要的情況下。

print multiply add 4 add 6 5 divide 1 square-root 3

訓練有素的眼睛可能知道從哪裡開始,但是這種混合使用函式作為引數的方式很快就會變得難以閱讀。

一種方法是將程式碼拆分成多行。在 Rebol 中,你可以完全自由地這樣做

print
  multiply
    add 4
      add 6 5
    divide
      1
      square-root 3

這有助於理解正在發生的事情。

print (4 + 6 + 5) * (1 / square-root 3)

是相同表示式的更簡單的等效形式。

外部參考

[edit | edit source]
華夏公益教科書