跳转到内容

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]
华夏公益教科书