Search code examples
prologtransitive-closure

How to express a transitive relationship


I want to express a transitive relationship. If A references B and B references C then A references C. I have this:

proj(A).
proj(B).
proj(C).
ref(A,B).
ref(B,C).

When I query using proj(A) I obtain:

[46] ?-proj(A).
A = _639

What does "_639" mean? I expected a yes or no and got that strangeness. I need to add a rule to say:

ref(A,C). and get YES. Ideally, if possible, I would like to show how this relationship came about: (A => B => C).


Solution

  • The _639 is an uninstantiated, anonymous variable. Your "facts" have variables rather than atoms. For example:

    proj(A).   % This has a variable A and means "any A is a project"
    

    So if I query:

    :- proj(X).
    X = _blah    % anonymous variable: anything is a project!
    

    You need atoms:

    proj(a).
    proj(b).
    

    Which results in the query:

    :- proj(X).
    X = a ;
    X = b 
    

    If you have:

    ref(a,b).
    ref(b,c).
    

    Then, the simplest way in Prolog to express a transitive property is by declaring a rule (or what's known as a predicate in Prolog):

    ref(A,C) :- ref(A,B), ref(B,C).
    

    This says that, ref(A,C) is true if ref(A,B) is true, AND ref(B,C) is true.. Running the query:

    :- ref(a,c).
    true ;
    Out of stack
    

    Or:

    :- ref(a,X).
    X = b ;
    X = c ;
    Out of stack
    

    So it sounds logical but has an issue: you can get into a loop due to the self-reference. A simple way around that is to make the rule name different than the fact:

    refx(A, B) :- ref(A, B).
    refx(A, C) :- ref(A, B), refx(B, C).
    

    Now if I query:

    :- refx(a, b).
    true ;
    no
    
    :- refx(a, c).
    yes
    
    :- refx(a, X).
    X = b ;
    X = c
    yes
    

    Etc.

    There are still cases where this could have termination issues, however, if the facts contain reflexive or commutative relationships. For example:

    ref(a,b).
    ref(b,a).
    ref(b,c).
    

    In this case, a query to refx(a, b) yields:

    | ?- refx(a, b).
    true ? ;
    true ? ;
    true ? ;
    ...
    

    As @lambda.xy.x points out, this could be resolved by tracking where we've been and avoiding repeat "visits":

    refx(X, Y) :-
        refx(X, Y, []).
    
    refx(X, Y, A) :-
        ref(X, Y),
        \+ memberchk((X, Y), A).   % Make sure we haven't visited this case
    refx(X, Y, A) :-
        ref(X, Z),
        \+ memberchk((X, Z), A),   % Make sure we haven't visited this case
        refx(Z, Y, [(X,Z)|A]).
    

    Now we terminate with refx(a,b) and succeed once:

    | ?- refx(a,b).
    true ? ;
    no
    | ?-
    

    And refx(X, Y) produces all of the solutions (albeit, some repeats due to succeeding more than once):

    | ?- refx(X, Y).
    
    X = a
    Y = b ? ;
    
    X = b
    Y = a ? ;
    
    X = b
    Y = c ? ;
    
    X = a
    Y = a ? ;
    
    X = a
    Y = c ? ;
    
    X = b
    Y = b ? ;
    
    X = b
    Y = c ? ;
    
    (2 ms) no