Rules: facts that reason
Rules with a head and a body, variables that start with a capital letter, and the search that reports one answer after another.
A fact with a condition
A rule says that something is true if some other things are true. You write the claim first, then the two characters :-, then the conditions separated by commas. A comma means and.
The conditions are called the body. tealover(X) :- likes(X, tea). reads as: somebody is a tea lover if that person likes tea. A name that starts with a capital letter is a variable, so it can stand for anyone. A lowercase name is the atom itself, so likes(ada, tea) asks about ada in particular.
The head can do more than name a variable. father(X) :- parent(X, Y), male(X). fixes part of the claim and leaves the rest to the body, so a rule can mix a pattern with conditions. Any goal in the body may be a fact, a rule or a comparison; the engine does not distinguish them once the clause is loaded.
Searching, and trying again
When a goal can be satisfied in more than one way, the engine reports each way in turn. That is what backtracking means: it remembers where it made a choice, and it goes back for another one when you want another answer.
A rule that prints inside that search prints once per answer, so one goal in the example gives two lines of output. Clauses are also tried in the order you wrote them, which is why the order of the facts in a file matters.
The search is depth-first. After matching the first tea lover, the engine follows that answer all the way through the remaining goals before it comes back for the second. If a later goal fails, the most recent choice is undone and the next alternative tried; if none is left, the failure travels further back. That is why a rule with a failing condition can take a long time before it reports no.
One rule over three facts
likes(ada, tea).
likes(ada, maths).
likes(bob, tea).
% The head is before :- and the body after it.
tealover(X) :-
likes(X, tea).
main :-
tealover(X),
write(X), write(' likes tea'), nl.
:- initialization(main).One goal, two answers
Run it and count the lines: a single goal produced two answers. Then add a third tea lover to the facts and run it again.
Two goals, one shared variable
Most useful rules have more than one condition. The comma between goals means “and”, and a variable that appears in both goals ties them together: share(A, B) :- likes(A, Thing), likes(B, Thing), A \== B. reads as “A and B share something if A likes a Thing and B likes the same Thing, and they are not the same person.” The repeated variable is the join.
The order of the goals is the order the engine tries them. It solves the leftmost one first and carries the bindings into the next, so likes(A, Thing) chooses a taste before the second goal looks for another person who likes it. Reordering the goals does not change which answers exist, but it changes how much searching the engine does to find them. Leave a variable free and the rule offers every match; fix both names and it simply succeeds or fails.
Ask share(ada, B). The first goal likes(ada, Thing) binds Thing to tea, then the second goal looks for another person who likes tea and finds bob. On backtracking Thing becomes maths and the second goal finds cy. The guard removes the answer where B is ada herself, so the two printed lines are the whole result.
What goes wrong
A variable used once is usually a mistake. tealover(X) :- likes(Y, tea). succeeds whenever anybody likes tea, and X stays free, so printing it shows an internal name. The warning about a singleton variable is the engine pointing at the name that does no work.
A rule that fails silently is harder. If the body asks for a fact that was never written, the rule simply has no answers and prints nothing; there is no error to read. Ask the body as a goal on its own, one comma at a time, to find which part fails. A missing full stop has the same quiet effect, because the next clause becomes part of the broken one.
Three mistakes worth catching early. A variable used only once says nothing about the answer, and its name suggests a link that is not there. A missing full stop turns two facts into one broken clause, so the second fact never gets read as a fact of its own. And a rule whose body silently fails prints nothing at all, so ask the body as a goal on its own before you blame the printing.
Try these next
- Add a fourth person who likes maths and count the answers to
share(ada, B)again. - Write a rule with three goals: two people share a taste, and neither is the person you started with.
- Swap the two
likesgoals inshare/2and check that the same pairs come back in a different order.
Two people, one taste
Run it and count the lines. Then add a fourth person who likes maths and see how many answers `share(ada, B)` gives.
The answers a rule gives are decided by the facts underneath it. Change one fact and every rule above it changes with it, which is the power of describing what is true instead of writing down steps.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
In tealover(X) :- likes(X, tea)., which part is the body?
