Weslin
Weslin

Reputation: 33

Recursive reference in prolog

I meet some problem when I try to implement

friends(mia, ellen).
friends(mia, lucy).
friends(X,Y) :-
  friends(X,Z),
  friends(Y,Z).

and when i ask ?- friends(mia, X)., it run out of local stack.

Then I add

friends(ellen, mia) friends(lucy, mia) 

I ask ?- friends(mia, X). ,it keeps replying X = mia.

I can't understand, why it is recursive?

Upvotes: 3

Views: 551

Answers (2)

repeat
repeat

Reputation: 18726

This clause of friends/2 is flawed:

friends(X,Y) :- friends(X,Z),friends(Y,Z).

Translate that into English: "If X and Y have a mutual friend Z, then X and Y are friends."

Or, let's specialize, let X be "me", let Y be my neighbour "FooBert", and let Z be "you": So if I am your friend and FooBert is your friend... does that make me and FooBert friends? I don't think so, I hate that guy---he always slams the door when he gets home. :)

I suggest you consider the algebraic properties that the relation friends/2 should have, the ones it may have, and ones it should not have. What about reflexivity, symmetry, anti-symmetry, transitivity?

Upvotes: 2

coredump
coredump

Reputation: 38799

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) ?

  1. First clause gives Y=ellen (you ask for more solutions)
  2. Second clause gives Y=lucy (you ask again for more solutions)
  3. Stack overflow !

Let's detail the third clause:

  1. I want to know if friends(mia,Y) holds for some variable Y.
  2. 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...

  3. We try the first two clauses of friends, but then we fail because there is no friends(ellen,Y) nor friends(lucy,Y), so...
  4. 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:

  1. known_friends/2, which gives direct relationships.
  2. 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.

Upvotes: 2

Related Questions