Previous Table of Contents Next


6.3.11.4. Unary Operators

Unary operators are declared in exactly the same way as binary ones. The four possible specifiers are fx and fy, for prefix operators, and xf and yf, for postfix operators. fx would be the natural specifier for the \+ operator, for instance, if we wanted to disallow expressions such as \+ \+ in(Dict, X); if we did want to allow such expressions, fy would be the appropriate specifier. xf might be something we would want to declare star as for a regular expression package so that we could use the term X star to mean zero or more repetitions of X.

An operator can be declared as both a prefix or postfix operator and as an infix operator. This is especially useful for the operator because we would like it declared both as an infix operator and as a prefix operator (for unary minus, as in -X). In fact, in most Prologs, it is declared as both, as if with the declarations

   :- op(500, yfx, -).
   :- op(200, fy, -).

6.3.11.5. Everything Is a Term

Now for another surprise, comparable to the surprise that lists are represented with function symbols (section 6.3.4.3). Most of the symbols we have encountered in the Prolog syntax are in fact operators, and everything that Prolog deals with is in fact a term!

Take the :- operator. We have not been accustomed to thinking of it as an operator, but it is treated as if declared with op(1200, xfx, :-). Moreover, the comma operator is treated as if declared with op(1000, xfy, ‘,’). Thus a clause such as

   grandfather(X, Y) :-
    parent(X, Parent),
    father(Parent, Y).

is treated just like the term

   ‘:-’( grandfather(X,Y),
        ‘,’( parent(X,Parent),
           father(Parent,Y)
         )
   )

Note the rather odd and overloaded nature of the comma here. When it appears within parentheses immediately after an identifier, it separates arguments of that function symbol or predicate; elsewhere, for instance at the top level of a body of a clause, it is treated as an operator no different from + or =.

One advantage of this uniform treatment of Prolog syntax as built up with operators is that Prolog can use the same algorithm when reading clauses in a source file as when reading terms from a file with read. A program is just terms terminated by dots like a data file. Another advantage is that we can read clauses from a source file in programs in exactly the same way that Prolog does. This is a useful thing to be able to do for metaprogramming, where we write programs that manipulate programs.

6.4. Tips and Traps

Here we discuss some useful hints for Prolog programmers and annoying pitfalls that they sometimes run into. These tips and traps are presented in what I think is their approximate order of importance.

6.4.1. Style

As in all programming languages, following any given style of coding is a matter of taste, but it is helpful to follow some style. Here I list some recommendations for good coding and design style, which programmers can follow or not as they wish.

6.4.1.1. Whitespace, Comments, and Naming

Because predicates can appear with any arity in Prolog, interpreters do not check whether the number of arguments in a particular predicate call is accurate. Hence it is helpful to code so that the number and position of arguments to a predicate call is apparent. In this chapter, I have followed the common convention of not putting any spaces anywhere in terms, but putting spaces after each comma in lists of arguments to predicates. Some writers go further and put a space between the left parenthesis beginning the list of arguments and the first argument.

It is good technique to associate one comment with each predicate definition, except for predicates whose meaning is obvious. I generally try to make the comment start with text of the form % pred(Arg1, Arg2):, followed by the description.

It is useful to line up the predicate calls in a predicate body. Naturally, there are times when this is not so appropriate, such as when a predicate body contains a sequence of write and nl calls that would otherwise stretch down the page. When using nested constructs, such as if-then-else, predicate calls on the same logical level in the code should be lined up.

Because predicates are intended to represent something declarative, it somewhat misses the point to name them using verbs, which represent non-declarative actions. Aside from some classic predicates such as append and delete, and predicates with obviously non-declarative meaning, such as write, predicates are usually named using nouns and adjectives in highly declarative code.

Many predicates are actually functions, in the sense that they take N-1 arguments as input and return their Nth argument as output. The common style in these situations is to have the returned argument as the last argument. I have attempted to follow this style in this chapter.

6.4.1.2. Design Patterns

As in Lisp, the most common pattern for predicates is one of recursing down a list. Many predicates will have the pattern

   p([], …) :-
    … .
   p([X|Xs], …) :-
    … .

Programmers should not avoid such code simply because they have done it before and are bored with it. Prolog is in fact optimized in some ways for such code (see section 6.4.7), and programmers should take advantage of this optimization.

Many interactive programs have one or more top-level predicates that are highly imperative, writing to and reading from the user and asserting and retracting persistent data. However, below a certain point in the predicate calling hierarchy, even interactive programs should be highly declarative; otherwise, there is little point in using a declarative language. It is useful to have interactive predicates grouped together in some source files and have other source files containing only declarative predicates. You can use the module facilities of your Prolog to link these together.


Previous Table of Contents Next