Variables in Prolog

Variables in Prolog are completely different to what you would expect if you already know another programming language. The first obvious difference is that thay have not associated data type. They are typeless. You can assign any value to a variable.

... unfortunately this is NOT ALL!

You can't actually change their values once they got assigned!

Initially, a variable has no value at all. Your mind may instinctively think of NULL values. Unfortunately, there is no NULL value in Prolog either.

First, let us look at feew of them. They begine with asterisk or star (*). For example: *x, *y, *something, *delta, are all variables. Initially, they have no value at all. You can check it usign pp command. Try: [pp [*x *y *z]] and you will see: [*0 *1 *2]. This means that the interpreter doesn't even know their names!. Empty, an unknown, no value.

However, they have one interesting attribute: *x is the same as *x. Try the following example: [pp [*x *y *x]] and you will see [*0 *1 *0]. This means that the interpreter is able to see which variables are the same.

You can assign values to variables by passing them as parameters to some instructions. For example: [sum 2 3 *x] will assign 5 to *x. There is also an explicit eq instruction, which does just this. Assigns a value to a variable (you can try [eq 5 *x]). Try the following code: [res [sum 2 3 *x] [pp *x]] and you will see 5.

Once a variable gets assigned it can not change its value. In fact, the variable disappears completely and is replaced by the value in the code. Try this: [res [eq 5 *x] [eq 8 *x]]. It won't work. The reasoning behind it is that *x can not be equal to 5 and 8 at the same time. However, in a more traditional computer language it would work. It is a very interesting situation.

Symbols

Symbols (sometimes called atoms) are the backbone of pretty much everything in Prolog. They can be compared to identifiers from other, more traditional computer languages. Identifiers are used to identify pieces of code (procedures or functions), variables, data types and pretty much anything else. Symbols are similar. They are used to identify clauses, which are similar to procedures. However, you can use symbols as values in calculations, which is rather difficult (or impossible) with identifiers.

Let us look at few examples of symbols. sum is a symbol. It is used to identify a clause respobsible for adding numbers. show or pp are symbols too.

It is possible to create symbols dynamically. It is also possible to create anonymous symbols as well. However, the lifes-span of such anonymous symbols is rather short. They are garbage collected as soon as they are not referenced from the code any more. Usually, they are good for one thing: local and unique symbols for local purposes.
[create_atom "atom"] creates symbol globally.
[create_atom "atom" *atom] creates anonymous symbol with specified name.
[create_atom *atom] creates anonymous symbol with random name.


As mentioned before, symbols can identify clauses. More precisely, symbols can have clauses attached to them. In general, those clauses can be ordinary Prolog clauses, such as facts and rules. However, you can also attach machine clauses to symbols. Most of the low-level instructions (such as I/O or MIDI transmisison) are defined as machine clauses.

It is easy to attach normal clause to a symbol. You just have to write such clause in the code. However, it is much more difficult to create a machine clause. One way, is to use SDK and create extra DLL module, which contains such clauses. But there is another way...

Some machine clauses take symbols as parameters. Then they create some other machine clauses and attach them to those symbools. A good example is just below.....

Traditional Variables (global)

Traditional variables are emulated in HERCs Prolog with symbols and machine clauses. The basic mechanism is that a machine clause emulate the behaviour of tranditional variables, while symbol is used as identifier.

1. First, we need a symbol. Let's say we will call our variable X. We can create a global symbol using create_atom instruction. Therefore, we will call: [create_atom "X"]. The quotation is necessary because X does not exist prior to the call.

2. Our X symbol has no clauses attached. You can check it using [list X] command. It will show nothing.

3. Now we will use one of the instructions, which create machine clause, that emulate traditional variables. A good candidate is var. The var instruction have two forms. The first form just "changes" symbols into "variables" (the default initial value will be emtpy list []). The second form also assigns initial values. We will call: [var X] or [var [X 0]] (if we want to initialise X to 0). If we want to "declare" more variables then we can use signle var instruction. I.e.: [var [X 0] [Y 1] [Z 3]] or [var X [Y 1] Z] (if we just need Y initialised).

4. You can now check that our X symbol has got a machine clause attached. Type: [list X], and you will see: X := machine.

5. It is now possible to assign values and retrieve them. To assign a new value (let's say 234) to X type: [X 234]. In fact, you can assign anything, including other symbols, pairs, lists, and so on.

6. The convention for retrieving values is to use colon. I.e. you will have to type: [X : *value] to get the current value of X. Here is a sequence of commands, which will display the value of X: [res [X :*value] [show *value]].

6. When you finish using the variable, you can type [X] (without parameters) to remove the attached machine clause. This way symbol X is now ready to be re-used. To confirm that X has no clauses attached, type: [list X].

Traditional Varables (local)

Interestingly, we can also emulate local variables in a similar way. The difference is that we will use anonymous symbols, instead of explicitly created ones. For example: [create_atom *X] will create an anonymous symbol and subsequent [var [*X 0]] will "change" it into "traditional variable". Please note, that var instruction will automatically create_atom if it gets variables instead of symbols. Therefore call to create_atom is not necessary. [var [*X 0]] will be just enough.

Such local variables don't even have to be "closed" (i.e. by calling [*X]). Once the anonymous symbol is not referenced any more, it is removed by garbage collector together with the attached machine claues and stored value.