5 Generic Prolog Programming

Question:
How can I split a module into several source code files?

You need a main file for the module, e.g. main.pl and several subfiles, e.g. sub1.pl, sub2.pl, ... Lay out the main file as follows:

     :- module(ModuleName, ExportList).
     
     ... clauses/directives ...
     
     :- ensure_loaded(sub1).
     
     .. clauses/directives ...
     
     :- ensure_loaded(sub2).

The subfiles can contain any clauses and directives, including ensure_loaded/1 directives, but not module/2 directives.

An alternative is to use an include declaration.

Question:
How can I make the compiler abort the compilation if it encounters a syntax error?

This can be done by defining user:message_hook/3 appropriately:

     | ?- [user:user].
     % consulting user...
     | message_hook(error,_,Lines) :- 
             print_message_lines(user_error,error,Lines), abort.
     | ^D
     % consulted user in module user, 0 msec 424 bytes

This will intercept any error message, print the message, and abort:

     | ?- [user].
     % consulting user...
     | p p.
     ! Syntax error
     ! operator expected after expression
     ! in line 39
     ! p
     ! <<here>>
     ! p .
     % consulted user in module user, 0 msec -16 bytes
     % Execution aborted
Question:
How can I write access predicates for terms without paying a performance penalty?

There is support for unfolding predicates at compile time: user:goal_expansion/3. For example, assume that is_ornode(X) (is_andnode(X)) is true if the shape (principal functor) of X is or/2 (and/2). If you consult the following:

     :- multifile
             user:goal_expansion/3.
     
     :- dynamic
             user:goal_expansion/3.
     
     user:goal_expansion(is_ornode(Term), _, Term=or(_,_)).
     user:goal_expansion(is_andnode(Term), _, Term=and(_,_)).
     
     plan_tree( [N|_Rest], _GuidanceNodes, _Indent ) :-
             is_andnode(N).
     plan_tree( [N|_Rest], _GuidanceNodes, _Indent ) :-
             is_ornode(N).

then plan_tree/3 becomes transformed to:

     plan_tree([A|_], _, _) :- A=and(_,_).
     plan_tree([A|_], _, _) :- A=or(_,_).

If you are using fcompile/1, make sure that the definition of user:goal_expansion/3, and anything else that the compiler needs to know, has been loaded at fcompile time. A common idiom is:

     | ?- ensure_loaded(SetOfFiles), fcompile(SetOfFiles).

Note that fcompile/1 is obsolescent with the introduction of partial saved-states (`.po' files).

Note also that plan_tree/3 will not be able to determinately select a matching clause based on A, as predicates are indexed on the shape of the first argument only, which is a list in both clauses. Achieving indexing on A is the subject of the next question.

Question:
Is SICStus capable of folding unifications across `:-' to determinately select a matching clause?

Consider the following clauses, with the above goal expansion:

     plan_tree( [N|_Rest], _GuidanceNodes, _Indent ) :-
             is_andnode(N).
     plan_tree( [N|_Rest], _GuidanceNodes, _Indent ) :-
             is_ornode(N).

In SICStus, as in most WAMs, indexing is done on the shape of the first argument. If all arguments are distinct variables A, B, C, ..., and the first goal is A = Term, indexing will be done on the shape of Term.

So to enable indexing on the shape of N, you must transform the clause e.g. to:

     plan_tree( [N|_Rest], _GuidanceNodes, _Indent ) :-
             plan_tree_flat( N, _Rest, _GuidanceNodes, _Indent ).
     
     plan_tree_flat(N, _Rest, _GuidanceNodes, _Indent ) :-
             is_andnode(N).
     plan_tree_flat(N, _Rest, _GuidanceNodes, _Indent ) :-
             is_ornode(N).

With the above goal expansion, this code will indeed index on N.

Question:
Why doesn't this Fibonacci program work?

Here's some code that purports to print all the Fibonacci numbers. The author of the code expected retract/1 to backtrack forever, finding ever new fibs/2 facts.

     print_fibs :-
             retractall(fib(_)),
             assert(fibs(1,1)),
             retract(fibs(F1,F2)),
             write(F1), nl,
             F3 is F1+F2,
             assert(fibs(F2,F3)),
             fail.

If you run it:

     | ?- print_fibs.
     1
     
     no

It doesn't work because of the semantics for calls to dynamic predicates in the presence of asserts and retracts. SICStus Prolog complies with the ISO Prolog standard in this respect. Clause 7.5.4 of the standard reads:

Any change in the database that occurs as the result of executing a goal (for example, when the activator of a subgoal is a call of assertz/1 or retract/1) shall affect only an activation whose execution begins afterwards. The change shall not affect any activation that is currently being executed.

In the above example, the retract/1 goal is unaffected by the subsequent assert, and only succeeds once.

Question:
The query X=[97|X], name(A,X). loops. Is it a bug?

It is possible, and sometimes useful, to write programs that unify a variable to a term in which that variable occurs, thus creating a cyclic term. The usual LP theory forbids the creation of cyclic terms, dictating that an occurs-check should be done each time a variable is unified with a term. Unfortunately, an occurs-check would be so expensive as to render Prolog impractical as a programming language. Thus cyclic terms may be created and may cause loops trying to print them.

SICStus Prolog mitigates the problem by its ability to unify, compare assert, and copy cyclic terms without looping. The write_term/[2,3] built-in predicate can optionally handle cyclic terms. Unification with occurs-check is available as a built-in predicate. Predicates testing (a)cyclicity are available in a library package. Other predicates usually do not handle cyclic terms well.

Question:
How can I have a predicate definition included when I load my code into Sicstus 3.10.x or earlier, but not when I load my code into Sicstus 3.11.0 or later?

You can use:

     | ?- prolog_flag(version,Version).

plus a bit of trivial parsing to determine the version number.

Question:
In this query, why is X not 29.51?
     | ?- X is 20.66 + 8.85.
     X = 29.509999999999998 ?
     yes

Floats like 20.66 and 8.85 are represented in the host computer's native floating-point format (almost universally, in IEEE 754 format). They are not represented as the precise rational numbers (2066/100) and (885/100). Rounding errors are bound to pop up whenever you do floating-point arithmetic, as your query illustrates. The exact same thing happens in C. The following C program illustrates the point: the floating-point sum (20.66+8.85) differs from 29.51 in the least significant bit.

     #include <stdio.h>
     
     unsigned int intone = 1;
     double d1 = 20.66;
     double d2 = 8.85;
     double d3 = 29.51;
     
     main()
     {
       double d4 = d1+d2;
       int mshalf = ((unsigned short *)&intone)[0];
       int lshalf = 1-mshalf;
     
       printf("20.66 + 8.85 with 15 decimals, and in hex:\n");
       printf("%.15f %0x%0x\n", d4, ((long *)&d4)[mshalf], ((long *)&d4)[lshalf]);
       printf("29.51 with 15 decimals, and in hex:\n");
       printf("%.15f %0x%0x\n", d3, ((long *)&d3)[mshalf], ((long *)&d3)[lshalf]);
       exit(0);
     }