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
现在,有用的地方在于,尽管我们可以使用它来测试多个条件,如上所示,但只要每个表达式返回的值不同于 false 或 none,我们就可以在块中执行多个任务。
all [ create-pdf-of-newsletter ftp-newsletter-to-website check-newsletter-not-corrupt update-webpage-last-change-date spam-our-customers ]
有趣的是,all 结构确保在我们完成任务列表和垃圾邮件之前,所有前面的表达式都必须为真。如果我们的网站更新没有完成,我们不希望这样做!
上面的示例利用了 all 的一个重要特性,即当它遇到 false 或 none 时不会进一步计算。这样,前面的条件可以“保护”后面的条件,例如
>> 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 的值(即任何不同于 false 和 none 的值)。同样,此属性不适用于 or 运算符。
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 参数为真(这意味着,不同于 false 或 none)时有条件地计算一个块。否则 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)为真(即不同于 false 和 none),否则计算它的第三个参数(false-block)。
>> either now/time < 12:00 [print "Good Morning"] [print "Good Afternoon"]
Rebol 中的真值与英语中使用相同的术语有所不同。任何不是 false 或 none 的东西都被视为真。这意味着所有整数,包括 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
switch 与 case 函数类似。但是,它接受一个值,然后将该值与一个值块对块进行比较。如果值匹配,则计算该块并返回其值,并在该点离开函数。如果找不到匹配项,则返回 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 ] ]
Loop
[edit | edit source]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** 函数。