First, two assumptions:
the actual code you wanted to write is the following one, with appropriate dots:
friends(mia,ellen).
friends(mia,lucy).
friends(X,Y) :-
friends(X,Z),
friends(Z,Y).
transivity holds: friends of friends are my friends too (I would rather model friendship as a distance: "A is near B" and "B is near C" does not necessarly imply "A is near C"). repeat's answer is right about figuring out first what you want to model.
Now, let's see why we go into infinite recursion.
Step-by-step
So, what happens when we ask: friends(mia,X)
?
- First clause gives
Y=ellen
(you ask for more solutions)
- Second clause gives
Y=lucy
(you ask again for more solutions)
- Stack overflow !
Let's detail the third clause:
- I want to know if
friends(mia,Y)
holds for some variable Y
.
Is there a variable Z
such that friends(mia,Z)
holds ?
Notice that apart from a renaming from Y
to Z
, we are asking the same question as step 1 above? This smells like infinite recursion, but let's see...
- We try the first two clauses of
friends
, but then we fail because there is no friends(ellen,Y)
nor friends(lucy,Y)
, so...
- We call the third clause in order to find if there is a transitive friendship, and we are back to step 1 without having progressed any further => infinite recursion.
This problem is analogous to infinite Left recursion in context-free grammars.
A fix
Have two predicates:
known_friends/2
, which gives direct relationships.
friends/2
, which also encodes transitivity
known_friends(mia,ellen).
known_friends(mia,lucy).
friends(X,Y) :- known_friends(X,Y).
friends(X,Y) :- known_friends(X,Z), friends(Z,Y).
Now, when we ask friends(mia,X)
, friends/2
gives the same answer as the two clauses of known_friends/2
, but does not find any answer for the transitive clause: the difference here is that known_friends
will make a little progress, namely find a known friend of mia
(without recursion), and try to find (recursively) if that friend is a friend of some other people.
Friends' friends
If we add known_friends(ellen, bishop)
:-) then friends
will also find Y=bishop
, because:
known_friends(mia,ellen)
holds, and
friends(ellen,bishop)
is found recursively.
Circularity
If you add cyclic dependencies in the friendship graph (in known_friends
), then you will have an infinite traversal of this graph with friends
. Before you can fix that, you have to consider the following questions:
- Does
friends(X,Y) <=> friends(Y,X)
hold for all (X,Y)
?
- What about
friends(X,X)
, for all X
?
Then, you should keep a set of all seen people when evaluating friends
in order to detect when you are looping through known_friends
, while taking into account the above properties. This should not be too difficult too implement, if you want to try.