Prolog Coding Guidelines 0
Prolog Coding Guidelines 0
Michael A. Covington
Artificial Intelligence Center, The University of Georgia
The following coding standards are inspired by Kernighan and Pike, The Practice of Programming, and my
own usual practices. This is a DRAFT document; feedback is invited. I want to thank Don Potter for a
number of helpful suggestions.
1 Internal documentation
1.1 Begin every predicate (except auxiliary predicates) with an introductory
comment in the standard format.
Predicates that can be called from elsewhere in the program from code written by others, or by yourself
on a dierent occasion must be properly documented. Here is an example:
% remove_duplicates(+List,-ProcessedList)
%
% Removes the duplicates in List, giving ProcessedList.
% Elements are considered to match if they can
% be unified with each other; thus, a partly uninstantiated
% element may become further instantiated during testing.
% If several elements match, the last of them is preserved.
The comment starts with a template consisting of the name of the predicate and its arguments.
Each argument is preceded by:
Rare indeed is the Prolog predicate that actually requires an argument to be uninstantiated (which is what
- ought to mean). Normally, - means only that the argument is normally uninstantiated.
Note that +, -, and ? are used only in comments. They are not part of the Prolog language.
After the template comes a clear explanation, in English, of what the predicate does, including special cases
(in this case, lists with uninstantiated elements).
Normally, you should write the comment before constructing the predicate denition. If you cannot describe
a predicate coherently in English, you are not ready to write it.
Comments of this type are not needed for auxiliary predicates, which exist only to continue the denition
of another predicate, and are not called from anywhere else. For example, recursive loops and subordinate
decision-making procedures are often placed in auxiliary predicates. Users of your code need not know about
auxiliary predicates.
1
1.2 Use descriptive argument names in the introductory comment; they need
not be the same as those in the clauses.
In the example just given, the names List and ProcessedList are used only in the comment. The actual
code will probably split the lists up with |. Youll see the actual code later in this document.
2 Predicate names
2.1 Make all names pronounceable.
A predicate named stlacie is going to confuse anyone else reading your program including yourself at a
later date even though it may seem, at the time of writing, to be a perfectly obvious way to abbreviate
sort the list and count its elements.
If you dont enjoy typing long names, type short ones. Call your predicate sac or even sc while typing in
your program, then do a global search-and-replace to change it to sort_and_count.
2.2 Never use two dierent names that are likely to be pronounced alike.
If you use foo, do not also use foo or fu.
People remember pronunciations, not spellings. Accordingly, it must be absolutely obvious how to spell a
name when all you remember is its pronunciation.
One regrettable computer program that I once saw used the names menutwo, menutoo, menu2, and (probably
by accident) mneu2.
2.3 Construct predicate names with lower-case letters, separating words with
underscores.
For example, write is well formed, not isWellFormed.
This is part of a scheme for mapping pronunciations to spellings consistently. If you have a predicate name
that is pronounced boo hiss, you should know immediately that you will spell it boo_hiss, not booHiss
or boohiss. If it were a variable name, it would be BooHiss (see below).
2
There are many examples of _aux in Prolog Programming in Depth and Natural Language Processing for
Prolog Programmers. However, _x is more concise and lends itself to making a sequence (_xx, _xxx, etc.
pronounced ex, ex ex, and so on).
2.9 Place arguments in the following order: inputs, intermediate results, and
nal results.
If you get into the habit of doing this consistently, its one less set of details to remember about each
individual predicate.
Intermediate results are those that represent a count so far or the like. They usually appear only in
auxiliary predicates.
3 Variable names
3.1 Use descriptive names for variables wherever possible, and make them ac-
curate.
For example, do not call a variable Tree if it is not a tree. You would be surprised at how often such things
are done by programmers who have changed their minds midway through constructing a clause.
3
3.3 For variables of purely local signicance, use single letters.
Specically, use:
I, J, K, L, M, N for integers;
L, L1, L2, L3. . . for lists;
C, C1, C2, C3. . . for single characters or ASCII codes;
A, B, C. . . , X, Y, Z for arbitrary terms;
H and T for head and tail of a list (when better names are not conveniently available).
3.4 Use a single letter for the rst element of a list and a plural name for the
remaining elements.
For example, match a list of trees to [T|Trees]. This is one of several strategies to keep names short but
signicant.
When the elements of the list do not need to be described, other convenient notations are [Head|Tail],
[H|T], and [First|Rest].
4 Layout
4.1 Use /* */ only to comment out blocks of code; use % for explanatory com-
ments.
The usual way to comment out part of a program is to put /* on a line before it and */ on a line after it.
This works only if the material that youre commenting out does not contain */.
write this:
Indented lists like this are much easier to read than lists disguised as paragraphs. It is also much less work
to add or remove an item or add comments about an item.
4
4.4 Indent all but the rst line of each clause.
For example, write:
remove_duplicates([First|Rest],Result) :-
member(First,Rest),
!,
remove_duplicates(Rest,Result).
remove_duplicates([First|Rest],[First|NewRest]) :-
% first element of List occurs only once in it
remove_duplicates(Rest,NewRest).
4.5 If a test is unnecessary because a cut has guaranteed that it is true, say so
in a comment at the appropriate place.
See rst element of List occurs only once in it in the previous example. The comment keeps the clause
from being misunderstood if the preceding clause is modied or removed.
4.6 Indent an additional 2 spaces between repeat and the corresponding cut.
This makes a repeat structure look more like a loop. Here is an example:
process_queries :-
repeat,
read_query(Q),
handle(Q),
Q = [quit],
!,
write(All done), nl.
4.7 Put each subgoal on a separate line, except closely related pairs such as
write and nl.
The preceding clause would be harder to understand if it were written like this:
process_queries :-
repeat, read_query(Q), handle(Q),
Q = [quit], !, write(All done), nl.
4.8 Skip a line between clauses. Skip two lines between predicates.
By establishing this practice in advance, you dont have to make a decision every time you come to the end
of a clause.
5
4.10 Consider redesigning any non-auxiliary predicate that has more than 4
arguments.
Predicates with too many arguments are usually trying to do several conceptually separate jobs at once.
%:sum_list(+ListOfNumbers,?Result)
%. Sums the numbers in the list, giving Result. Crashes with an
%. error message if first argument is not a list of numbers.
5 Expressing algorithms
5.1 Invest appropriate (not excessive) eort in the program; distinguish a pro-
totype from a nished product.
Many AI programs are exploratory; when trying to gure out whether something can be done, it is reasonable
to do each part of it in the easiest possible way, whether or not its ecient.
6
5.2 The most ecient program is the one that does the right computation, not
the one with the most tricks.
Using eciency tricks, you can sometimes double the speed of a computation. By choosing a better basic
algorithm, you can sometimes speed a computation up by a factor of 1,000,000 or more.
Do not modify your code just for the sake of eciency until you are sure it is actually doing the right
computation.
colorful(X) :-
color(X,Y),
( red(Y) ; green(Y) ).
colorful(X) :-
color(X,Y),
bright(Y).
bright(red).
bright(green).
7
5.8 Use parentheses whenever operator precedence is important; do not assume
that people have memorized the precedence table.
Quick, is x,y;z equivalent to x,(y;z)? Rather than trying to remember, use explicit parentheses.
(a -> b ; c)
is pronounced if a then b else c and means Try a, and if it succeeds, do b and cut; otherwise do c. It is
a way of placing a Pascal or C if-then-else structure in the middle of a Prolog clause.
In general, this is a rather un-Prolog-like way of thinking. See the discussion in Prolog Programming in
Depth.
5.11 Look out for constructs that are almost always wrong.
These include:
a cut at the end of the last clause of a predicate (exactly what alternatives is it supposed to eliminate?);1
a repeat not followed by a cut (when will it stop repeating?);
append with a one-element list as its rst argument (use the element and | instead).
8
5.15 Use tail recursion for recursions of unknown depth.
Tail recursion is recursion in which the recursive call is the last subgoal of the last clause, leaving no
backtrack points. In this circumstance, execution can jump to the beginning of the predicate rather than
creating another goal on the stack. See Chapter 4 of Prolog Programming in Depth.
5.16 Recognize that tail recursion is unimportant when the depth of the recur-
sion is limited to about 50 or less.
Do not sacrice simplicity and clarity to gain a tiny amount of eciency in these cases. But do use tail
recursive algorithms when they are as clear and simple as their non-tail-recursive counterparts.
5.17 Avoid assert and retract unless you actually need to preserve information
through backtracking.
Although it depends on your compiler, assert and retract are usually very slow. Their purpose is to store
information that must survive backtracking. If you are merely passing intermediate results from one step of
a computation to the next, use arguments.
6 Reliability
6.1 Isolate non-portable code.
Suppose you are writing an SWI-Prolog program that passes some commands to Windows. You might be
tempted to put calls such as
...,
win_exec(X,normal),
...
pass_command_to_windows(X) :- win_exec(X,normal).
Elsewhere in your program, call pass_command_to_windows, not win_exec. That way, when you move to
another compiler in which win_exec has a dierent name or works dierently, youll only need to change
one line in your program.
9
6.2 Isolate magic numbers.
If a number occurs more than once in your program, make it the argument of a fact. Instead of writing
X is 3.14159*Y
in one place and
Z is 3.14159*Q
somewhere else, dene a fact:
pi(3.14159).
and write
pi(P), X is P*Y
and so forth.
This example may seem silly because the value of never changes. However, if you encode 3.14159 in only
one place, you dont have to worry about mis-typing it elsewhere.
And if a number in your program really does change a number denoting an interest rate, or the maximum
size of a le, or something then its very important to be able to update the program by changing it in
just one place, without having to check for other occurrences of it.
6.3 Take the extra minute to prevent errors rather than having to nd them
later.
This is a basic rule. Dont take the extra hour just take the extra minute. In particular, think through
loops. For example, given the predicate
count_up(10) :- !.
count_up(X) :- write(X),
Y is X+1,
count_up(Y).
what is the rst number printed when you count up from 1? The last number? What happens if you try
to count up from 11, or from 0.5? A minutes careful thought at the right time can save you an hour of
debugging on the computer.
6.5 Test that each loop starts correctly, advances correctly, and ends correctly.
In count_up:
What is the rst number printed?
After printing a particular number (such as 5), what gets printed next?
What is the last number printed?
Thats the essence of understanding any loop: you have to know where it starts, how it advances from one
value to the next, and where it stops.
10
6.6 Test every predicate by forcing it to backtrack.
Its not enough to try
?- count_up(5).
?- count_up(5), fail.
and see what happens when count_up is forced to backtrack. This will show you why the rst clause of
count_up has to contain a cut.
?- count_up(What).
The most common arguments of the wrong type are uninstantiated arguments. Beware they match
anything!
6.8 Do not waste time testing for errors that will be caught anyhow.
If you type
?- count_up(five).
the program crashes with an arithmetic error. Thats probably okay; the important thing is that you should
know that it will happen.
Do not burden your Prolog programs by testing the type of every argument of every predicate. Check only
for errors that (1) are likely to occur, and (2) can be handled by your program in some useful way.
6.9 In any error situation, make the program either correct the problem or
crash (not just fail).
In Prolog, failing is not the same as crashing. Failing is the same as answering a question no. No can be
a truthful and informative answer, and it is not the same as saying, Your question does not make sense,
or I cannot compute the answer. An example:
?- square_root(169,13).
yes
?- square_root(169,12).
no % makes sense because 12 is not the square root of 169
?- square_root(georgia,X).
no % misleading because the question is ill-formed
In the last case the program should complain that georgia is not a number.
11
6.10 Make error messages informative.
Whenever your program outputs an error message, that message must actually explain what is wrong, and,
if possible, display the data that caused the problem. It should also indicate where the error was detected
(e.g., by giving the predicate name). A good example:
That doesnt say what data is the wrong type or where it was found.
6.14 Use write(!!!) to mark places in the program where work remains to
be done.
This will keep you from forgetting them.
end
12