Rebol Programming/Language Features/Control

REBOL offers a variety of different control structures with many similar to those seen in other computer languages, and some unique to itself.

All
all: native [ {Shortcut AND. Evaluates and returns at the first FALSE or NONE.} block [block!] "Block of expressions" ]

all is a useful function that allows one to check to see if all the expressions in a block evaluate to true or not. It obviates the need for cascading the either constructs.

>> all [true true true] == true

>> all [true true false] == none

Now, the useful thing is that although we can use it to test for multiple conditions as above, we can perform multiple tasks inside the block as long as each expression returns a value distinct from false or none.

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

Interestingly, the all construct ensures that all of the preceding expressions must be true before we complete our list of tasks, and spam-our-customers. We wouldn't want to do this if the update to our website had not completed!

The above example takes advantage of an important property of all which is that it does not evaluate further when it encounters false or none. This way the preceding conditions can "guard" the subsequent ones, like e.g.:

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

The same would not be true when using the and operator:

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

NB: Although the help for all says that it is shortcut for and, it is not really. So, note the following

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

where the return value is none and not a logic! false.

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

any returns true if any of the expressions inside the block returns true.

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

Similarly as with all, any "guards" the subsequent conditions, if it encounters a value considered true (i.e. anything distinct from false and none). Again, this property does not hold for the or operator.

If, Either and Unless
if: native [ "If condition is TRUE, evaluates the block." condition then-block [block!] /else "If not true, evaluate this block" else-block [block!] ]

The if function allows one to conditionally evaluate a block if the condition argument is true (which means, distinct from false or none). Otherwise if returns none. If the /else refinement is used, then the second block is evaluated in case the condition is false.

While condition can be the result of a simple expression such as

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

'''Warning: the  refinement may not be supported in the future versions of Rebol, being considered obsolete! It is faster and future-proof to use the  native instead.'''

The condition can be a result of any legal function that returns a value. So, if you had a function named web-update? that checks to see if a particular page has been updated in the last few days, you could write

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

The either native function is conditionally evaluates its second argument (the true-block), if the first argument (the condition) is true (i.e. distinct from false and none), otherwise it evaluates its third argument (the false-block).

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

Truth in Rebol is somewhat different from the English language use of the same term. Anything that is not false or none is treated as true. This means that all integers, including 0, are treated as true as well as all blocks, etc.

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

Beginner gotcha: you should especially take care of:

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

, because the block is treated as true. The same holds for 0:

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

In place of the if not combination, we can use the unless function. Example:

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

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

Case
The general case expression form can be:

case [ condition1 block1 condition2 block2 etc.   ]

Conditions are evaluated and checked one after another. If true, the block following the condition is evaluated and returned as the case result.

Case enhances readability replacing nested either. Example:

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"] ]

The example above would have been almost unreadable when written using either. Notice the true condition intended to yield "Hello dear" if none of the previous conditions was true.

Switch
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 is similar to the case function. However, it takes a single value, and then compares that to a block of value block pairs. If the value matches, it evaluates the block, and returns its value leaving the function at that point. If there is no match, it returns none. If the default refinement is specified, then the optional last block is evaluated.

Let us say that we wish to implement a POP3 server. We know that the POP3 server is state driven, and has 2 states, an authorisation state, and a transaction state. In each of these states, the POP3 server will only accept certain commands.

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"]

The authorisation switch might look like this, where the words in the blocks are pseudocode.

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"]

In both of the above examples we have used word values for the switch, but any Rebol value can be used.

switch 3 [ 3 [print "3 received"] 4 [print "4 received"] ]

For
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 is much like the traditional for loop structure seen in other languages with a small twist - the increment, or bump amount, can be in a datatype other than integer! as is usually required.

So, if you wish to write a loop where the counter value is incremented in a money! type, we can do this:

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

giving

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

In case the repeat control function is suitable for the job, prefer repeat, it is faster.

Forall
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

The most used loop in Rebol is foreach. But with foreach we can not change the current value as it does not give us the series-position. forall was created for that case as it gives us the position instead of the current value.

As the series is traversed, the current value becomes the first element of the series.

We can change the first element like this:

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

>> my-series == [1 4 9 16]

In the example above, all members of my-series are squared.

Another way to access the series is with path-notation, like my-series/1. Then it looks

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

which is sometimes more comfortable.

The other advantage that forall gives you over foreach is that you have access to the index of the current element within the series being traversed.

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

Note that 'my-series is altered in the loop. Under certain circumstances that change stays even if the loop terminated. That happens on a break, or on an error, and is intentional.

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

>> my-series == [3 4]

In pre view1.3/core 2.6 releases of Rebol, the series stayed at the tail on completion of the loop which was a source of confusion for many.

Thats why you find forall series [..] series: head series in some older code.

Forskip
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 is similar to forall in that it traverses a series while making the current element the first element of the series. It is different in that forskip allows you to move through the series by a specified integer amount. From 1.3, it also reset the series on completion. The same advantages forall has over foreach apply to forskip. Example:

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

produces the following output

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

Say we now wanted to add 1000 to the area code. Here's one way of doing that using 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

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

And here's an alternate way of doing this using the path notation which removes the necessity to reverse the series:

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

Foreach
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 is arguably the most common repetition structure used in Rebol. Like the majority of repetition structures, it does not affect the series it traverses.

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

In this example, the current directory is read and stored as a series in the word files. In the foreach structure, the temporary variable file is assigned the value of the current element in the series, and then printed to the console. The temporary variable file is local only to the evaluation block.

This can also be written as

foreach file read %./ [ print file ]

doing away with the necessity to define the word files. If you really do want the word files, you can also do this

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

saving one line in your source code.

If your series comes in pairs, you can create a pair of temporary variables

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

which prints the first name, a space, the surname, and then starts a newline.

Forever
Forever evaluates a block endlessly unless terminated at the console with the ESCAPE key, or a function inside the block.

What is it used for? Well, it could be used to increase one's Karmic credits! In Tibet mechanical prayer wheels are used to spin prayers such as "Om Mani Padme Hum". His Holiness the Dalai Lama has stated that computers can be used for the same effect (since the hard drive is also spinning).

So, here we have a Rebol prayer wheel in action

forever [ print "Om Mani Padme Hum" ]

However, a more common usage of forever is to set up a server loop

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
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 allows you to evaluate a block a specified number of times unless exited inside the block. However, there is no local counter accessible, so if you wish to track at which repetition, you will have to create your own variable for this. Alternatively you could use the for function.

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

loop returns the value of the last execution of the block, and is here assigned to result. If we had used print instead, we would have had an error as print does not return a value.

Repeat
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 evaluates a block, and has a local counter available. If the repetition value is an integer, then the counter starts at one, and reaches the repetition value.

repeat value 3 [ print value ] 1 2 3

If it is a series, then the "counter" takes the value in the series

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

The final value is returned.

Until
Until is a control flow function checking the condition at the end of evaluation of its block argument. USAGE: UNTIL block

DESCRIPTION: Evaluates a block until it is TRUE. UNTIL is a native value.

ARGUMENTS: block -- (Type: block)

until evaluates a block repeatedly until the block evaluates to true.

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

Here we prompt the user to input a password. If it matches the secret value, a true value is obtained, and we exit the block. We ensure that the test is case sensitive by using == rather than the case insensitive =.

While
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 differs from until in that the body-block may not be evaluated, whereas in until the body block is always evaluated at least once.

While also differs from similar control flow constructs in other programming languages by actually evaluating the condition "anywhere" in the repetition. It can generally be used as follows:

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

, where neither the pre-check stuff, nor the post-check stuff are necessary.

This example is equivalent to forever

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

, and it is a case when the condition is evaluated "at the start of a repetition", since no evaluation precedes the true expression in the cond-block.

Another example how to write an infinite loop using while:

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

, in this case the condition is actually evaluated "at the end of a repetition", since nothing is done after the cond-block (yielding the true value) is evaluated and before another repetition starts.

It is quite common to use while to skip through a series

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

Break
There is a couple of methods that can be used to stop the repetition of any repetitive control function. Specifically we can use the break function. Compare the following code with the general example used in the while section:

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

The break function has got a /return refinement enabling us to cause the repetitive control function to return a specific value.

There are two other methods which can be used to stop the repetition. We can use the return or exit function if the repetition is done in a function or the throw function, if the repetition is done in a catch block.