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.
Logic Functions
editAll
editall: 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
editany: 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.
Conditionals
editIf, Either and Unless
editif: 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 /else
refinement may not be supported in the future versions of Rebol, being considered obsolete! It is faster and future-proof to use the either
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
editThe 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
editUSAGE: 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"] ]
Repetition
editFor
editUSAGE: 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
editUSAGE: 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
editUSAGE: 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
editUSAGE: 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
editForever 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
editUSAGE: 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
editUSAGE: 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
editUntil 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
editUSAGE: 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
editThere 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.