Rebol Concepts

Introduction

The Rebol documentation describes most of this in succinct way. Rebol itself is remarkably simple, elegant and powerful. To help the learning curve, this article describes some Rebol fundamentals using different phrasing.

The world according to Rebol

Before getting into the language proper it is worth looking at just what is it that Rebol does with the script that you feed it.

First, Rebol is an interpreted language with minimal syntax. So if you have been using compiled programming languages you should try to forget about them and their related concepts while you learn Rebol.

Rebol scripts are plain text. They can be entered at the console prompt, read from a file, embedded in HTML, etc. In order for the script to become executable it has to be loaded into Rebol. Loading for Rebol means recognising specific forms of syntax and assigning these datatypes. This action results in Rebol values.

Points to note:

After the values have been loaded from a script they can be evaluated. Some values just evaluate to themselves, for example the number 42 will just evaluate to itself. Other values, such as functions, when evaluated result in new values.

Here are some forms that Rebol recognises. The table shows example forms and the datatypes of the values that result when Rebol loads them.

example form

datatype

1

integer!

3.17

decimal!

31-dec-2001

date!

http://localhost

url!

harry@harrysdomain.com

email!

"some text"

string!

{another "sentence"}

string!

dog

word!

[ 1 3.17 dog "test" ]

block!

Refer to the Rebol user guide for a description of the other forms that Rebol recognises and the datatypes that they have when loaded.

The last example form shown above is more complex in that it is composed of a number of simpler forms. The datatype of this form is block! which is a type of container value that holds a sequence of other Rebol values. The Rebol values held in a block can be any type of Rebol value.

Some special values

Words

Words (word!) are an interesting and fundamental feature of Rebol.

A Rebol word is a symbol that can be given a meaning. That is they have the useful property of being able to be used as names for other values.

For any given word there can be multiple textual forms that relate to it. The different forms represent different ways of treating the word.

The simplest form for a word is the word written by itself. When Rebol loads such a form it results in a datatype of word!. Evaluating a word! value returns the value that the word is set to. That is a mouthful, and it may not make sense until you read the other examples below. Let's return to it a bit later.

This next bit of script is a form that uses the single quote character. When loaded and evaluated in Rebol it results in a word with the name "age":

'age

This textual form is recognised in rebol as the lit-word! datatype. The lit-word! datatype when evaluated results in the word itself.

Here is another form that is associated with the same word as the previous example:

age:

This form when recognised by Rebol results in a value of datatype set-word!. It is still dealing with the word "age" but the evaluation of it is different. When evaluated set-word! values set the word they are associated with to the value immediately following their occurrence in Rebol script. So the example above as it stands would result in an error because it is missing a value. Here then is a working example:

age: 3

How about getting the value of the word with name "age"? Well the get-word! datatype when evaluated has that behaviour. It looks like this:

:age

Earlier I mentioned that the simplest form of a word results in a word! datatype and that when evaluated results in the value that the word is set to. So this bit of script will also result in a 3:

age

The behaviour "age" and ":age" here appear exactly the same. There is a difference when the value in question is not a 3 but a function. If word age is a function then the simplest form for age ("age") will end up resulting in the function being evaluated. The get-word! form for age (":age") will result in a value of type function (which is not evaluated further)!.

The set-word! and get-word! forms are not the only ways to set and get the value of words. Two Rebol functions have exactly those actions - they are SET and GET respectively. For example, SET sets the value of a word. It has two arguments the word and the value. To specify the word argument you need a form that evaluates to the word which was shown in the first example of this section:

SET 'age 3

I mentioned earlier that everything in Rebol is a value. Here's an interesting example that combines nearly everything I've shown so far:

name-of-word: 'age
SET name-of-word 3

This last example before evaluation (and after load) is a script containing five values. The values are in order:

set-word! lit-word!
word! word! integer!

Once the first two values have been evaluated, the word "name-of-word" will be set to a value of "age" with datatype word!. When the evaluations of the following three values are complete the word "age" will be set to a value of 3 with datatype integer!.

Functions

Functions (function!) are a special type of Rebol value. When they are evaluated, any values following them are bound to their arguments by position. What that means is that when for example you enter "SUBTRACT 6 3" you end up with a result of 3 not -3.

Here is an example of creating a function, with no name because functions in Rebol don't need a name:

func [x] [subtract 6 x]

Most people refer to functions by name, and having been through the words section earlier it should be obvious now how to create a function and refer to it by name:

add-three: func [x] [add x 3]

Remember, that everything in Rebol is value? What about this:

subtract-x: func [x] [subtract 6 x]

Well that last line is composed of four values:

set-word! word! block! block!

The word "func" refers to a function that takes two arguments. The example could have been written like this:

arg-block: [x]
body-block: [subtract 6 x]
set 'subtract-x func arg-block body-block

Not quite as nice, but exactly the same result.

Blocks

I mentioned before that blocks are a container type. An ordered sequence of values. Actually they are an ordered sequence of un-evaluated values. What that means is that when a block itself is evaluated the values it contains are not evaluated. The values a block contains are evaluated when a function is applied to the block.

For example, this block which I've shown earlier contains three values:

[subtract 6 x]

The first value in that block is a word "subtract", but because it has not been evaluated it is no more than just a word. If at some point it is evaluated only then will the Rebol function that does subtraction come in to play. For example if you entered that block in the console you would get this session:

>> [subtract 6 x]
== [subtract 6 x]

The block evaluates to itself.

Now however if I apply a function to it...:

>>  print [subtract 6 x]
** Script Error: x has no value
** Where: do-boot
** Near: subtract 6 x

So "subtract" evaluates to a function, 6 evaluates to itself, and x is evaluated but because it not set, an error is returned.

Now I want to make a general point. Rebol does not make a distinction between the traditional concepts of code and data. Rebol script is both - evaluation of the values determines the actions and results.

Changeable values

It is a suprising fact that nearly every cell of the adult human body would not have existed at birth. Cells die and are replaced - yet we think of having a single body over a lifetime.

Some Rebol values are like that. Take strings for example. In Rebol you can modifiy a string by adding or removing characters from it, yet it remains a single value. This does not seem that suprising, but consider this: two words can be set to the same string. I like to think of the two words as referring to the same string. If the string is changed both words when evaluated will show the modified string:

some-text: "The blue fox"
story-title: some-text
replace story-title "blue" "quick brown"
print some-text

If you run that in the console you'll see this result:

The quick brown fox

Not every value exibits this changeable behaviour. Integers are an example. Values which do are pretty much all the container types: series, objects.

First trap for beginners

There a script structure that almost always traps someone new to Rebol.

It involves the concept of changeable values I mentioned earlier.

Consider this function:

add-two-stars: func [] [
    text: ""
    append text "*"
    append text "*"
    print text
]

On a casual reading most people interpret this function as setting a variable called "text" to an empty string. Then adding a star to that string twice. Finally printing the result. It looks too simple to be doing otherwise. The fun happens when you evaluate the function more than once:

>> add-two-stars
**
>> add-two-stars
****
>> add-two-stars
******

The incongruity between our expectation and the actual results comes down to the first line of the function where "text" is set to an empty string.

The answer is this. The empty string is a value which is created *once* during load of the block that represents the body of the function. The function is based on a copy of this block and therefore gets this empty string when it is created.

When the function is run, the empty string is no longer empty. This no longer emtpy string referred to by the word "text" is stored within the body of the block. Thus the definition of the function while still exactly the same appears to be changing each run. You can see this if you used the Source function after the two runs - like this:

>> source add-two-stars
add-two-stars: func [][
    text: "****"
    append text "*"
    append text "*"
    print text
]

Now let's modify the function definition to be closer to expectations:

add-two-stars: func [] [
    text: copy ""
    append text "*"
    append text "*"
    print text
]

The only change was to add the COPY function before the empty string. The empty string is created *once* as before, but each time the function is run now the word "text" is set to refer to a copy of the empty string not the empty string itself. Problem solved. You'll see the same sort of thing with other "changeable values".