readability
See first the articles on commenting, indentation, and white space. Code needs to be readable so that readers, who might be you in a few days, or you in six months, or another programmer in six months or six years, can readily understand what you are trying to do.
Perhaps the first principle is to use meaningful and accurate names for variables and predicates. If your predicate is supposed to check if a number is even, you can call it is_even
. If you later change it so that it also calculates the contribution, for the case of even numbers, to some total that you are calculating, then you need to change the name of the predicate to reflect this. Maybe you can call it even_contribution
, and put a comment where the predicate starts, stating exactly what the predicate does, in more detail than you can fit into a predicate name.
Don't reinvent things already available in Prolog.
Here's an example of some code produced by a novice Prolog programmer:
mymember(First, [Second | _]) :- member(First, [Second]).
First of all, member(First, [Second])
is identical to First = Second
. So why not say so:
mymember(First, [Second | _]) :- First = Second.
But then, why have the explicit unification of First
and Second
- so instead:
mymember(First, [First | _]).
At this point, we can see that mymember is a misleading name for this predicate - which is actually checking whether its first argument is the same as the first member of the list that is the second argument. So the name of the predicate is misleading, as well as unclear. member
, in Prolog, means check the whole list for the presence of the first argument, whereas this predicate only ever looks at the first element of the list. The work that this predicate is doing has no obvious name, and that suggests that it is not a useful abstraction of the problem being solved. The problem that the novice programmer was trying to solve was to find out if a list had successive elements that were the same, as happens for b
, but not a
. in the list [a, b, b, c, d, a]
. So a better way would be like this:
has_repeats([]) :- fail. % unnecessary rule - see below has_repeats([_OnlyOneItem]) :- fail. % unnecssary rule - see below has_repeats([First, First | Rest]). has_repeats([First, Second | Rest]) :- First = Second, has_repeats([Second | Rest]).
In this code, the rule in red is the one that corresponds to what the novice programmer was trying to do with "mymember
".
Notice also that the rules using fail
can be omitted - they are only there to make it clear what happens with empty lists and lists with one member.
Don't have unused "junk" code. Obviously, it is worse still to have used junk code - that is, wrong code. However, unused junk code is confusing for the reader and demonstrates that the programmer who wrote it was confused. So how can you tell if code is unused junk? The best way, I suppose, is to have a thorough understanding of the code you are writing, and then you'll see that the code is junk. Failing that, when you test the code, you can put in calls to the built-in Prolog predicate print
, at the start of any rule you are unsure about.
thing(X, Y) :- print('Entering rule 3 for predicate *thing*'), ... % rest of goals for this rule .
Then you try to generate a test case which will make use of your rule in finding it's first solution:
?- thing(A, [cat, dog, mouse]). % replace with actual parameters % that are supposed to test rule 3 of the predicate called thing A = 3.
If, as in the example dialogue, the print
never executes, or never executes as part of a successful search for a solution, then it's probably junk.
recursion
A recursive rule is one which refers to itself. That is, its body includes a goal which is an instance of the relation that appears in the head of the rule. A well-known example is the member
procedure:
member(Item, [Item|Rest]).
member(Item, [_|Rest]) :- member(Item, Rest).
The second clause is clearly recursive - member
occurs both in the head and the body. A recursive procedure can work because of two things:
member
) in the body is in some way simpler than that in the head, so that Prolog is being asked to solve a simpler problem. (In member
, the list that is the argument to member
in the body is shorter by one than that in the head.)member
, the first clause is the trivial branch - it says that member
succeeds if Item
is the first member of the list that is the second argument.)
A recursive data structure is one where the structures include substructures whose principle functor is the same as that of the whole structure. For example, the tree structure:tree(tree(empty, fred, empty), john, tree(empty, mary, empty))
is recursive. Recursive data structures require recursive procedures to process them.
It can help, in understanding recursion, to separate the different depths of recursive invocation of Prolog rules by drawing boxes around the parts that correspond to a particular invocation, and giving separate (but systematic) names to the variables in each invocation. So if the rule involves a variableX
, then in the first invocation, we refer to X
as X1
, while in the second (recursive) invocation, we refer to the new X
as X2
, and so on.
Let's try this out with the recursive proceduresumlist
. In each recursive call of sumlist
, there is a separate instance of the variables First
, Rest
, Sum
, and SumOfRest
, and these are distinguished by subscripts - so First1
is the instance of First
in the top-level call of sumlist
, and First2
is the instance of First
in the first recursive call to sumlist
.
% sumlist(NumList, SumOfList) - binds SumOfList to the sum % of the list NumList of numbers. % Rule 1: sumlist([], 0). % Rule 2: sumlist([First | Rest], Sum) :- sumlist(Rest, SumOfRest), % Goal 2.1 Sum is First + SumOfRest. % Goal 2.2
and the query
?- sumlist([5, 2, 1], Answer).
sumlist[5, 2, 1], Answer). Choose Rule: only rule 2 matches, with First1 = 5; Rest1 = [2, 1]; Sum1 = Answer Goal 2.1: sumlist(Rest1, SumOfRest1),
Sum1 is First1 + SumOfRest1. |
Try doing a Prolog trace of the same query and procedure, and compare the results.
Here are some tips on writing recursive procedures in Prolog..
See also mutual recursion.
relation
The word relation, in Prolog, has its usual mathematical meaning. See relations and functions if you are not sure about what this is. A unary relation is a set of objects all sharing some property. Example:
is_dog(fido). is_dog(rex). is_dog(rover).
A binary relation is a set of pairs all of which are related in the same way. Example:
eats(fido, biscuits). eats(rex, chocolate). eats(rover, cheese). eats(lassie, bone).
Similarly for ternary relations (triples) and so on. In the examples above, is_dog
and eats
are the functors for the relations. Prolog stores information in the form of relations, or more specifically relational instances, like those above, and also in the form of rules.
It is also possible in Prolog to have nullary relations (a relation with no arguments), though they are perhaps not often used. Example:
program_untested.
This could be used as a goal in a query:
?- program_untested. true.
Possibly a better way to do this would be to use a unary relation:
program_status(untested).
However, the point is that nullary relations are there in Prolog, should you find a use for them.
See also the the next entry for more detail.