3.3 Using the QuLog Interpreter

qulog is the name of the QuLog interpreter. From a Unix shell, QuLog is started by typing:

qulog

or

qulog -A name

where name is a name for this QuLog process that will be registered with a Pedro server running on the same host. You need to use this option if you want to be able to receive and/or send messages to other processes that have similarly registered a different name with this Pedro server.

If the Pedro server is running on a different host identified by domain or IP address Host, launch QuLog using

qulog -N Host -A name

For example

qulog -N leo.itee.uq.edu.au  -A keith_agent

When the interpreter is ready it will prompt you with

| ??

At this point, a relation query or an action command followed by a FULLSTOP NEWLINE can be entered. The interpreter will check that the query or command is syntactically and type correct and that modes of use are correct. It will either display an error message or print out a response to the query or command.

A CONTROL-D will exit the interpreter whenever you get the prompt.

CONTROL-C will interrupt an evaluation and allow you to abort the interpreter (enter e in response to the interrupt prompt), or to terminate the current query and any forked action threads (enter r in response to the interrupt prompt), giving you the | ?? query prompt again. There are other response options, displayed if you enter ? in response to the interrupt prompt.

If you enter a relation query then either ’no’ will be displayed to indicate there are no solutions to the query or bindings for variables of the query with their minimal types will be displayed separated by lines of fullstops. If you entered a command (action) any output from the command will be displayed followed by ’success’. Unlike relation queries, commands cannot fail or produce multiple solutions, but they can raise exceptions.

When there are multiple solutions to a relation query the first five (if there are that many) are displayed separated by lines containing ...

Example:

| ?? X in [4,0,3,4].

X = 4 : nat
...
X = 0 : nat
...
X = 3 : nat
...
X = 4 : nat

| ??  % New prompt indicates all solutions have been given

If there are five or more solutions the interpreter waits for input from the user before displaying more. The two usual responses are:

NEWLINE - no more solutions are required; or

..NEWLINE - asking for up to 5 more solutions.

Example, showing a second use of ’in’:

| ?? S in "Apple".

S = "A" : string
...
S = "p" : string
...
S = "p" : string
...
S = "l" : string
...
S = "e" : string
..      % Request for more answers if there are any
no more solutions 
% Above displayed only after .. input and there are no more answers

| ?? X in {6,2,3,0,3,7,4}.

% {6,2,3,0,3,7,4} is a set so second 3 ignored, third use of 'in'

X = 0 : nat    % Answers displayed in value order 
...
X = 2 : nat
...
X = 3 : nat
...
X = 4 : nat
...
X = 6 : nat
..        % Request to display up to 5 more answers
X = 7 : nat

| ??      % Prompt for next query indicating no more answers

If you feel that the interpreter is giving back too many, or too few answers for a particular problem you can control this in two ways. The first is to prefix the query by the number of solutions you would like displayed at a time, followed by a of, followed by the query. Also, instead of simply using a .. to ask for more solutions you can change the number of solutions to be displayed for this query to positive integer k by entering ..k.

Example:

| ?? 1 of X in [1,2,1,4,2].  % Answers 1 at a time

X = 1 : nat
.. 2                % Switch to sols 2 at a time
X = 2 : nat
...
X = 1 : nat
..                  % Request for the next 2 sols
X = 4 : nat
...
X = 2 : nat
..                  % Request for the next 2 sols
no more solutions
| ?

You can also change the default number of solutions that are displayed for any query to a positive number n, say 3, using the command:

| ?? set_num_answers(3).

success

Sometimes you might have a relation query that contains many variables but you might only be interested in the bindings for some of the variables. This can be accomplished by listing the variables for which you want to see the answer bindings, separated from the query by a ?.

Example:

| ?? L1, L2 :: append(L1, L3, [1,2,3,4,5,6]) & append(L4, L2, L3)
     & 2 = #L4.
     % Expressions such as  #L4, length of L4, can be used in = tests
L1 = [] : list(_)
     % A type variable _ (don't care) as [] is empty list of any type
L2 = [3, 4, 5, 6] : list(nat)
     % (nat) is type expression for a list of nats (non-neg ints)
...
L1 = [1] : list(nat)
L2 = [4, 5, 6] : list(nat)
...
L1 = [1, 2] : list(nat)
L2 = [5, 6] : list(nat)
...
L1 = [1, 2, 3] : list(nat)
L2 = [6] : list(nat)
...
L1 = [1, 2, 3, 4] : list(nat)
L2 = [] : list(Ty1)

The two ideas above can be combined as in the following example.

| ?? 2 of L1, L2 :: append(L1, L3, [1,2,3,4,5,6]) & append(L4, L2, L3) & 
     2 = #L4.

L1 = [] : list(Ty1)     
L2 = [3, 4, 5, 6] : list(nat)
...
L1 = [1] : [nat]
L2 = [4, 5, 6] : list(nat)

Equivalently you can express the above query using an existential quantification on L3, L3.

| ?? 2 of exists L3, L4 append(L1, L3, [1,2,3,4,5,6]) &
        append(L4, L2, L3) & 2 = #L4.

If you want to evaluate an expression in the interpreter, one way of doing this is to simply create a unification involving a variable and the expression of interest. Remember that expressions are evaluated before unification.

Example:

| ?? X = 2+sin(pi()/4).

X = 2.70711 : num

| ?? X = cos.

X = cos : atom_naming(fun(num) -> num)


The second expression is just the name of a primitive function and the value is that function. Note that cos is an atom that names a function.

Instead of getting each solution of a query displayed (as in the previous append example) you can use a list comprehension expression with unwanted variables existentially quantified as below.

| ?? Lst = [(L1,L2) :: exists L3, L4 (append(L1, L3, [1,2,3,4,5,6]) &
            append(L4, L2, L3) & 2 = #L4)].

Lst = [([], [3, 4, 5, 6]), ([1], [4, 5, 6]), ([1, 2], [5, 6]),
       ([1, 2, 3], [6]),
       ([1, 2, 3, 4], [])] : list((list(nat), list(nat)))


% The value type is a list of pairs of lists of nats

We can re-express the last list expression query more succinctly using the list concatenation operator <> for splitting of a list using the special non-deterministic match operator =? that requires its left hand side to be, or to evaluate to, a ground term.

| ?? Lst = [(L1,L2) :: exists L4 [1,2,3,4,5,6] =?
                                 L1 <> L4 :: (2 = #L4) <> L2].

Using this non-deterministic list pattern matching we do not need the L3 variable, and the constraint that L4 must contain two elements becomes a constraint

2=#L4

expressed inside the <> pattern expression attached to the variable L4, preceded by a ::.

If you have constructed a program file prog1.qlg of QuLog type definitions, type declarations for relations, function and actions and their rules, you can bring all those into the interpreter using either the Prolog consult


| ?? [prog1].

Consulting prog1...
... prog1 consulted 
success

or the command


| ?? consult prog1.

Consulting prog1...
... prog1 consulted 
success

You may get syntax and mode errors signalled in which case none of the program file is consulted. There will be at least one QuLog examples file with the QuLog distribution that you installed. You can consult and query one of these files. For example, there may be a file qlexamples.qlg in qulog/examples/introduction. If you launch QuLog from inside this directory you can load all its definitions using:

| ?? [qlexamples].

You can see all the relation and function rules you currently have in the interpreter using:

| ?? show.

or specific ones using:

| ?? show child_of, person, fact, new_child.

You may use any sub-names and show will match any names containing the given sub-names. Notice the variable names of the consulted file will be used.

You can see all the type definitions and declarations using:

| ?? types.

You can see all the system type definitions and the type declarations for the primitive relations, functions and actions using:

| ?? stypes.

A displayed type declaration may be accompanied by a brief description of the primitive. You can also show the type declarations for specific relations by giving their names, separated by commas, after either the types or stypes command.

It’s also possible to consult files from within a program (as in Prolog) using a line in the program like

?- [prog1]
?- [prog2]

Constraints on using consult

For this discussion we say the file consulted at the interpreter level is the primary file and any files consulted in the primary file or any file consulted from within those programs are secondary files.

Whenever programs are consulted, type and mode checking is applied and if, at the interpreter level, a file is re-consulted then we need to check that changes to programs have not introduced type and mode errors. In principle, this means we should remove all user definitions and declarations (both from the primary and secondary files) and do a fresh consult of the primary file.

Typically, when a user is debugging a program, only the primary file will be edited by the user and so it would be inefficient to remove all definitions and declarations from the secondary files and start again. In order to make this process more efficient we constrain the use of consult in two ways in order to make this easier. The first is that, in any interpreter session, the user can only consult one file with re-consulting that file allowed (i.e. only one primary consult). The second is that all consults appearing in a program file are at the start of the file (before declarations and definitions).

When any file is consulted, a fact containing the file name and a timestamp (last update time) is remembered. When the primary file is reconsulted the first step is to check if any secondary files has a more recent last updated time than the remembered fact (meaning the file has been updated). In this case we take the conservative approach and clear out all user definitions and declarations and do a “fresh consult”.

If no secondary files have changed and the primary file has also not changed then the consult does nothing as nothing has changed.

Otherwise, all the definitions and declarations of the primary file are removed and the file is re-consulted. If it turns out that one of the changes to the primary file is to change what files are consulted (including the order of consults) then this could lead to potential type mode problems and so, again, we take a conservative approach and remove all user definitions and declarations and do a fresh consult.

By doing the above, we believe that there is no significant impact on the way users write and debug programs. If fact, the most typical debugging approach of editing the definitions and declarations of only the primary file will be quite efficient as only the old definitions and declarations for the primary file will be removed and the new code consulted (without re-consulting the secondary files).


On This Site