Search code examples
prologparent-childfailure-slicetransitive-closure

Prolog - 2 ways for progenitor predicate implementation


Given a set of facts that represent parent-child relationships via the predicate parent/2, what are the differences when defining the relation "progenitor"(ancestor) with the predicates pred1/2 and pred2/2 as shown below?

pred1(X,Z) :- parent(X,Z).
pred1(X,Z) :- parent(X,Y), pred1(Y,Z).

pred2(X,Z) :- parent(X,Z).
pred2(X,Z) :- parent(Y,Z), pred2(X,Y).

Solution

  • The major difference are the termination properties of both, provided there are some cycles in the relation. To understand this, I will use a failure-slice which cuts down the program to its essence w.r.t. termination.

    pred1(X,Z) :- false, parent(X,Z).
    pred1(X,Z) :- parent(X,Y), pred1(Y,Z). % Z has no influence
    
    pred2(X,Z) :- false, parent(X,Z).
    pred2(X,Z) :- parent(Y,Z), pred2(X,Y). % X has no influence
    

    For pred1/2, the second argument has no influence on termination at all, whereas in pred2/2, the first argument has no influence. To see this, try the original definitions with the fact:

    parent(a,a).
    
    ?- pred1(b, _), false.
       false. % terminates
    ?- pred2(b, _), false.
       loops.
    ?- pred1(_, b), false.
       loops.
    ?- pred2(_, b), false.
       false. % terminates
    

    See closure/3 for a general approach to avoid such loops.

    For completeness, here is another variation of transitive closure which has some conceptual advantages:

    pred3(X,Z) :- parent(X,Z).
    pred3(X,Z) :- pred3(X,Y), pred3(Y,Z).
    

    Alas, its termination properties are worst. In fact, it never terminates, as is testified by the following fragment:

    pred3(X,Z) :- false, parent(X,Z).
    pred3(X,Z) :- pred3(X,Y), false, pred3(Y,Z).
    

    In this fragment, the first argument is only passed on. So, no matter, what the arguments are, the program will always loop!

    ?- pred3(b,b), false.
       loops.