跳轉到內容

Rebol 程式設計/語言特性/控制

來自華夏公益教科書

REBOL 提供了多種不同的控制結構,其中許多與其他計算機語言中的結構相似,也有一些是 REBOL 獨有的。

邏輯函式

[編輯 | 編輯原始碼]
all: native [
   {Shortcut AND. Evaluates and returns at the first FALSE or NONE.}
   block [block!] "Block of expressions"
]

all 是一個有用的函式,允許檢查塊中的所有表示式是否都計算為真。它消除了級聯 either 結構的需要。

>> all [true true true]
== true
>> all [true true false]
== none

現在,有用的地方在於,儘管我們可以使用它來測試多個條件,如上所示,但只要每個表示式返回的值不同於 falsenone,我們就可以在塊中執行多個任務。

all [
   create-pdf-of-newsletter
   ftp-newsletter-to-website
   check-newsletter-not-corrupt
   update-webpage-last-change-date
   spam-our-customers
]

有趣的是,all 結構確保在我們完成任務列表和垃圾郵件之前,所有前面的表示式都必須為真。如果我們的網站更新沒有完成,我們不希望這樣做!

上面的示例利用了 all 的一個重要特性,即當它遇到 falsenone 時不會進一步計算。這樣,前面的條件可以“保護”後面的條件,例如

>> x: 0
== 0
>> all [x <> 0 1 / x < 2]
== none

當使用 and 運算子時,情況就不一樣了

>> (x <> 0) and (1 / x < 2)
** Math Error: Attempt to divide by zero
** Near: 1 / x < 2

注意:雖然 all 的幫助資訊說它是 and 的快捷方式,但實際上並非如此。所以,請注意以下情況

>> all [ true ]
== true
>> all [ true false ]
== none

其中返回值是 none 而不是邏輯!false

any: native [
   {Shortcut OR. Evaluates and returns the first value that is not FALSE or NONE.}
   block [block!] "Block of expressions"
]

any 如果塊中的任何表示式返回 true,則返回 true

>> any [false false false true]
== true

all 相似,any “保護”後面的條件,如果它遇到一個被認為是 true 的值(即任何不同於 falsenone 的值)。同樣,此屬性不適用於 or 運算子。

條件語句

[編輯 | 編輯原始碼]

If, Either 和 Unless

[編輯 | 編輯原始碼]
if: native [
    "If condition is TRUE, evaluates the block."
    condition
    then-block [block!]
    /else "If not true, evaluate this block"
    else-block [block!]
]

if 函式允許在 condition 引數為真(這意味著,不同於 falsenone)時有條件地計算一個塊。否則 if 返回 none。如果使用 /else 修飾,則如果 condition 為假,則計算第二個塊。

條件可以是簡單表示式的結果,例如

>> if/else 5 < 10 [print "less than 10"] [print "greater than or equal to 10"]
less than 10

警告:/else 修飾在未來的 Rebol 版本中可能不受支援,被認為是過時的!使用 either 原生函式更快且更具未來性。

條件可以是任何返回值的合法函式的結果。因此,如果您有一個名為 web-update? 的函式,用於檢查某個特定頁面是否在過去幾天內更新過,您可以這樣寫

>> if web-update? http://www.rebol.com [print "Rebol Tech has updated their website!"]

either 原生函式有條件地計算它的第二個引數(true-block),如果它的第一個引數(condition)為真(即不同於 falsenone),否則計算它的第三個引數(false-block)。

>> either now/time < 12:00 [print "Good Morning"] [print "Good Afternoon"]

Rebol 中的真值與英語中使用相同的術語有所不同。任何不是 falsenone 的東西都被視為真。這意味著所有整數,包括 0,都被視為真,以及所有塊等。

>> if [] [print "even an empty block is true"]
even an empty block is treated as true

初學者陷阱:你應該特別注意

>> either [false] [true] [false]
== true

, 因為該塊被視為真。0 也一樣

>> either 0 [true] [false]
== true

我們可以使用 unless 函式代替 if not 組合。例如

>> if not 2 > 3 ["OK"]
== "OK"
>> unless 2 > 3 ["OK"]
== "OK"

一般 case 表示式形式可以是

   case [
       condition1 block1
       condition2 block2
       etc.
   ]

條件逐個計算和檢查。如果為真,則計算條件後面的塊並將其作為 case 的結果返回。

Case 增強了可讀性,取代了巢狀的 either。例如

wife-birthday: 1-March-1960
Valentines-day: 14-Feb-1900
wedding-anniversary: 1-April-1980
anniversary?: func [
    date [date!]
    {check to see if today's date is an anniversary of the argument}
][
    all [date/day = now/day date/month = now/month]
]

greeting: case [
    anniversary? wife-birthday ["Happy birthday dear"]
    anniversary? Valentines-day ["Happy Valentines dear"]
    anniversary? wedding-anniversary ["Happy anniversary dear"]
    true ["Hello dear"]
]

上面的示例使用 either 編寫時幾乎不可讀。請注意 true 條件,如果前面的條件都不為真,則它將返回“Hello dear”。

USAGE:
   SWITCH value cases /default case

DESCRIPTION:
    Selects a choice and evaluates what follows it.
    SWITCH is a function value.

ARGUMENTS:
    value -- Value to search for. (Type: any)
    cases -- Block of cases to search. (Type: block)

REFINEMENTS:
    /default
        case -- Default case if no others are found. (Type: any)

(SPECIAL ATTRIBUTES)
    throw

switchcase 函式類似。但是,它接受一個值,然後將該值與一個值塊對塊進行比較。如果值匹配,則計算該塊並返回其值,並在該點離開函式。如果找不到匹配項,則返回 none。如果指定了 default 修飾,則計算可選的最後一個塊。

假設我們想實現一個 POP3 伺服器。我們知道 POP3 伺服器是狀態驅動的,有兩種狀態:授權狀態和事務狀態。在每種狀態下,POP3 伺服器只接受某些命令。

state: 'transaction
command: 'RETR
..
..
switch/default state [
    transaction [
        ... contains another switch to check for valid transaction commands ...
    ]
    authorisation [
        ... contains another switch to check for valid authorisation commands ...
    ]
] [print "Unknown state error"]


授權 switch 可能如下所示,其中塊中的單詞是虛擬碼。

switch/default command [
   user [check to see if valid user syntax]
   pass [check to see if valid username and password combination]
   apop [change authentication method]
   quit [close connection and wait for next user]
] [print "Unknown command in authorisation mode"]

在以上兩個示例中,我們都使用 word 值作為 switch,但可以使用任何 Rebol 值。

switch 3 [
    3 [print "3 received"]
    4 [print "4 received"]
]
USAGE:
   FOR 'word start end bump body

DESCRIPTION:
    Repeats a block over a range of values.
    FOR is a function value.

ARGUMENTS:
    word -- Variable to hold current value (Type: word)
    start -- Starting value (Type: number series money time date char)
    end -- Ending value (Type: number series money time date char)
    bump -- Amount to skip each time (Type: number money time char)
    body -- Block to evaluate (Type: block)

(SPECIAL ATTRIBUTES)
    catch
    throw

for 與其他語言中常見的傳統 for 迴圈結構非常相似,只有一個小的變化 - 增量或增加量可以是除整數以外的資料型別!通常是必需的。

因此,如果您想編寫一個迴圈,其中計數器值以 money! 型別遞增,我們可以這樣做

for opinion $0 $.10 $0.02 [
    print ["here is my 2c again totalling " opinion]
]

得到

here is my 2c again totalling  $0.00
here is my 2c again totalling  $0.02
here is my 2c again totalling  $0.04
here is my 2c again totalling  $0.06
here is my 2c again totalling  $0.08
here is my 2c again totalling  $0.10

如果 repeat 控制函式適合該工作,請優先使用 repeat,它更快。

USAGE:
   FORALL 'word body

DESCRIPTION:
    Evaluates a block for every value in a series.
    FORALL is a function value.

ARGUMENTS:
    word -- Word set to each position in series and changed as a result (Type: word)
    body -- Block to evaluate each time (Type: block)

(SPECIAL ATTRIBUTES)
    catch

Rebol 中最常用的迴圈是 **foreach**。但 **foreach** 無法修改當前值,因為它沒有提供系列位置。**forall** 為這種情況而建立,因為它提供位置而不是當前值。

在遍歷系列時,當前值成為系列的第一個元素。

我們可以像這樣更改第一個元素

my-series: [1 2 3 4]
forall my-series [ 
    change my-series (first my-series) * first my-series
]
>> my-series
== [1 4 9 16]

在上面的示例中,my-series 的所有成員都被平方。

另一種訪問系列的方法是使用路徑表示法,例如 my-series/1。然後它看起來像

my-series: [1 2 3 4]
forall my-series [ 
    my-series/1: my-series/1 * my-series/1
]

這有時更方便。

**forall** 相對於 **foreach** 的另一個優勢是,您可以訪問系列中正在遍歷的當前元素的索引。

forall my-series [
    print index? my-series
]

請注意,'my-series 在迴圈中被修改。在某些情況下,即使迴圈終止,這種變化也會保留。這種情況發生在中斷或錯誤時,這是故意的。

my-series: [1 2 3 4]
forall my-series [ 
    if 3 = first my-series [break]
]
>> my-series
== [3 4]

在 Rebol 的 pre view1.3/core 2.6 版本中,系列在迴圈完成時保持在尾部,這對許多人來說是一個混淆源。

這就是為什麼你發現

forall series [..]
series: head series

在一些舊程式碼中。

Forskip

[edit | edit source]
USAGE:
   FORSKIP 'word skip-num body

DESCRIPTION:
    Evaluates a block for periodic values in a series.
    FORSKIP is a function value.

ARGUMENTS:
    word -- Word set to each position in series and changed as a result (Type: word)
    skip-num -- Number of values to skip each time (Type: integer)
    body -- Block to evaluate each time (Type: block)

(SPECIAL ATTRIBUTES)
    throw
    catch

**forskip** 與 **forall** 類似,因為它在遍歷系列時將當前元素設定為系列的第一個元素。它與 **forskip** 不同的是,**forskip** 允許您按指定整數量在系列中移動。從 1.3 版本開始,它還會在完成時重置系列。**forall** 相對於 **foreach** 的所有優點也適用於 **forskip**。示例

areacodes: [
   "Ukiah"         707
   "San Francisco" 415
   "Sacramento"    916
]
forskip areacodes 2 [
    print [first areacodes "area code is" second areacodes]
]

產生以下輸出

Ukiah area code is 707
San Francisco area code is 415
Sacramento area code is 916

假設我們現在要將 1000 新增到區號。這裡有一種使用 **forskip** 完成此操作的方法

reverse areacodes ; so that the area code is the first element
forskip areacodes 2 [
    change areacodes add 1000 first areacodes
]
reverse areacodes ; and set it back to the original format

得到

[
   "Ukiah" 1707
   "San Francisco" 1415
   "Sacramento" 1916
]

這裡還有另一種使用路徑表示法完成此操作的方法,它消除了反轉系列的必要性

forskip areacodes 2 [
    areacodes/2: add 1000 areacodes/2
]

Foreach

[edit | edit source]
USAGE:
   FOREACH 'word data body

DESCRIPTION:
    Evaluates a block for each value(s) in a series.
    FOREACH is a native value.

ARGUMENTS:
    word -- Word or block of words to set each time (will be local) (Type: get-word word block)
    data -- The series to traverse (Type: series)
    body -- Block to evaluate

**foreach** 可以說是 Rebol 中最常見的重複結構。與大多數重複結構一樣,它不影響它遍歷的系列。

files: read %./
foreach file files [
    print file
] 

在本示例中,當前目錄被讀取並作為系列儲存在詞 **files** 中。在 **foreach** 結構中,臨時變數 **file** 被分配系列中當前元素的值,然後列印到控制檯。臨時變數 **file** 僅在評估塊中是區域性的。

這也可以寫成

foreach file read %./ [
    print file
]  

這樣就無需定義詞 **files** 了。如果你真的想要詞 **files**,你也可以這樣做

foreach file files: read %./ [
    print file
]  

這樣可以節省你原始碼中的一行。

如果你的系列成對出現,你可以建立一個臨時變數對

names: ["Joe" "Bloggs" "Jane" "Doe"]
foreach [first-name surname] names [
    print [first-name " " surname]
]

它會列印姓氏,一個空格,然後是名字,最後開始一個新行。

Forever

[edit | edit source]

**Forever** 不斷地評估一個塊,除非在控制檯中使用 ESCAPE 鍵或塊內的函式終止。

它有什麼用呢?它可以用來增加一個人的業力!在西藏,人們使用機械轉經筒來旋轉祈禱文,例如 "嗡嘛呢唄美吽"。達賴喇嘛尊者曾說過,計算機可以起到同樣的作用(因為硬碟也在旋轉)。

因此,這裡有一個正在執行的 Rebol 轉經筒

forever [
    print "Om Mani Padme Hum"
]

然而,**forever** 更常見的用法是建立一個伺服器迴圈

   listen: open tcp://:12345
   waitports: [listen]
   forever [
       data: wait waitports
       either same? data listen [
           active-port: first listen
           append waitports active-port
       ][
           incoming-from-remote: first data
           print incoming-from-remote        
       ]     
   ]
USAGE:
   LOOP count block

DESCRIPTION:
    Evaluates a block a specified number of times.
    LOOP is a native value.

ARGUMENTS:
    count -- Number of repetitions (Type: integer)
    block -- Block to evaluate (Type: block)

**loop** 允許您評估一個塊指定的次數,除非在塊內退出。但是,沒有可訪問的本地計數器,因此,如果您希望跟蹤重複次數,您將不得不為其建立自己的變數。或者,您可以使用 **for** 函式。

result: loop 3 [print "hail Mary"]
"Hail Mary"
"Hail Mary"
"Hail Mary"

**loop** 返回塊最後一次執行的值,並在此處分配給 **result**。如果我們使用 **print**,我們會遇到錯誤,因為 **print** 不返回值。

Repeat

[edit | edit source]
USAGE:
   REPEAT 'word value body

DESCRIPTION:
    Evaluates a block a number of times or over a series.
    REPEAT is a native value.

ARGUMENTS:
    word -- Word to set each time (Type: word)
    value -- Maximum number or series to traverse (Type: integer series)
    body -- Block to evaluate each time (Type: block)

**repeat** 評估一個塊,並提供一個本地計數器。如果重複 **value** 是一個整數,那麼計數器從一開始,並達到重複 **value**。

repeat value 3 [
    print value 
]
1
2
3

如果它是一個系列,那麼 "計數器" 將採用系列中的值

repeat value ["a" "b" "c"] [
    print value
]
a
b
c

返回最終值。

Until

[edit | edit source]

**Until** 是一個控制流函式,它在評估其 **block** 引數後檢查條件。

USAGE:
   UNTIL block
DESCRIPTION:
    Evaluates a block until it is TRUE.
    UNTIL is a native value.
ARGUMENTS:
    block -- (Type: block)

**until** 不斷地評估一個塊,直到 **block** 評估為真。

until [
   prin "Enter password: "
   pass: input/hide
   pass == "Uncle"
]

這裡,我們提示使用者輸入密碼。如果它與秘密值匹配,則獲得一個真值,我們退出塊。我們透過使用 **==** 而不是不區分大小寫的 **=** 來確保測試區分大小寫。

While

[edit | edit source]
USAGE:
   WHILE cond-block body-block

DESCRIPTION:
    While a condition block is TRUE, evaluates another block.
    WHILE is a native value.

ARGUMENTS:
    cond-block -- (Type: block)
    body-block -- (Type: block)

**while** 與 **until** 的不同之處在於,**body-block** 可能不會被評估,而在 **until** 中,**body** 塊總是至少被評估一次。

**While** 與其他程式語言中類似的控制流構造的不同之處在於,它實際上是在 "任何地方" 評估條件。它通常可以按以下方式使用

while [
    do some pre-check stuff
    need-more-work? ; the check
] [
    do some post-check stuff
]

,其中預檢查內容和後檢查內容都是不必要的。

此示例等效於 **forever**

while [true] [
    print "S.O.S."
]

,這是條件在 "一次重複的開始" 處評估的情況,因為在 **cond-block** 中的 **true** 表示式之前沒有評估。

另一個使用 **while** 寫無限迴圈的示例

   while [print "Om Mani Padme Hum" true] []

,在這種情況下,條件實際上是在 "一次重複的結束" 處評估的,因為在 **cond-block**(產生 **true** 值)被評估後和另一次重複開始之前什麼也沒有做。

使用 **while** 跳過系列很常見

while [not tail? my-series: next my-series] [
   do something here
]

Break

[edit | edit source]

有幾種方法可以用來停止任何重複控制函式的重複。具體來說,我們可以使用 **break** 函式。將以下程式碼與 **while** 部分中使用的通用示例進行比較

while [true] [
    do some pre-check stuff
    unless need-more-work? [break]
    do some post-check stuff
]

**break** 函式有一個 **/return** 細化,使我們能夠讓重複控制函式返回特定值。

還有兩種其他方法可以用來停止重複。如果重複是在函式中完成的,我們可以使用 **return** 或 **exit** 函式,如果重複是在 **catch** 塊中完成的,我們可以使用 **throw** 函式。

位運算子

[edit | edit source]

異或

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