Previous | Table of Contents | Next |
One of the most common mistakes of new Prolog programmers is to overuse assert and retract. It has been said that a Fortran programmer can write Fortran programs in any language. Although not all programmers may be quite that bad, nevertheless it takes a programmer used to the imperative style quite a while to adapt to a programming language without imperative modification of data.
To avoid this trap, remember that Prolog predicates are typically going to have more parameters than the analogous procedures in an imperative language. Some of these parameters will be data structures that it is necessary to search at some point; it will not always be possible to dump data into Prolog predicates and get Prolog to do the searching for us. Prologs searching is much more appropriate for structures of facts and rules that are common to the entire program execution, rather than for the changing data structures of a particular run of a program.
This trap is associated with a way of thinking that tries to understand and direct Prologs execution strategy. This is like trying to ride a bucking bronco: Unless you are skillful, you will probably get thrown. Instead of thinking in these terms, it is often useful simply to try to express in logic the problem to be solved. You may end up with a solution that is inefficient, or even does not terminate due to the way it is coded. However, you can then proceed from the logic-based starting point to make the code more efficient as well as logical. This will help you understand the code much more thoroughly as it is being developed.
You may notice warnings of singleton variables from your interpreter concerning some of the examples in this chapter. Most Prologs check every clause that they encounter to see whether any variable has been used only once in the clause. The appearance of one of these singleton variables usually highlights a bug because experienced programmers use what are called anonymous variables to avoid singletons.
Any sequence of alphanumeric characters and underscores starting with an underscore is also a valid variable name. These underscore variables have the property that no two occurrences of the variable in a clause are connected with one another; the variable is in some sense nameless, or anonymous.
Anonymous variables are useful when we dont care about the value of some parameter. For instance, the member predicate can be re-expressed as follows:
member([X|_], X). member([_|Xs], X) :- member(Xs, X).
This makes use of the simplest anonymous variable _ in two places where we dont care what the value of that part of the list argument is for the purposes of the clause. When we use anonymous variables in every place where we dont care about a value, every non-anonymous variable gets used at least twice.
Why does the Prolog system care enough to warn us about places where a variable comes up only once? Consider the following code:
process_list([], []). process_list([X|Xs], [NewX|NewXs]) :- process_element(X, NewX), process_list(Xs, New_Xs).
You may not be able to see the problem with this code at first glance, but there is a problem. The variable name NewXs is used in the head of the second clause, but New_Xs is used later. The result of the recursive call does not get passed back, as was probably intended; instead, a list with an uninstantiated variable is passed back. Bugs such as these are very common and characteristically manifest themselves as one or (more commonly) two singleton variables in a clause. Prologs report on singleton non-anonymous variables but not singleton anonymous variables; so if we replace all intentional singletons with anonymous variables, the warnings we will end up with will highlight these misspellings.
_ is sometimes called the anonymous variable, but any identifier beginning with an underscore is an anonymous variable. Sometimes these other variable names are useful for reminding us what is supposed to go in that location while retaining their anonymity:
member([X|_Xs], X). member([_X|Xs], X) :- member(Xs, X).
This code maintains the information that the members of the list are all the same kind of thing as far as the member predicate is concerned.
Occasionally, you may encounter a term in printout that literally goes on forever: The system keeps printing and printing until we break out of the program. This is usually due to a deficiency in most Prolog interpreters, which the program has happened to encounter.
When Prolog is given a query of the form X = [a|X], it should respond with no; after all, we cant find any term to substitute for X in that query that makes it true. Prolog should check to see whether X occurs in the term to the right of the = before trying to substitute.
Many Prolog interpreters do not perform this occurs check simply because it takes a long time and it very rarely results in a problem. Equality, and unification of terms for matching calls with clause heads, is one of the basic operations of a Prolog interpreter and is executed several times on each inference step. It must be made as efficient as possible.
Because variables are really represented by pointers, the incorrect processing of the query X = [a|X] takes the following form: X is set to a data structure with two pointers, of which the left pointer points to a and the right pointer points back to the term itself. Thus when Prolog goes to print the solution to the query, it typically prints
X = [a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,
and so on until we break.
The solution to the problem is either to turn on the occurs check on systems that have that option or else to code around the problem. The latter solution is more common because (except in application areas such as metaprogramming) problems with the occurs check very often result from bugs, rather than a genuine need to perform the check.
Previous | Table of Contents | Next |