Disjunction G1 ; G2 vs. If-then-else Cond -> G1 ; G2

Viewed 125

I encountered a Prolog program containing a nested if-then-else of the form

p(X,Y) :-
     (cond1(X,Y) -> q(X)); true,
     (cond2(X,Y) -> q(Y)); true.

that had unexpected answers. The reason for this behaviour is the same as in the following queries:

?- (true, (true -> X=a)); X=b.
X = a ;
X = b.

?- ((true -> X=a), true); X=b.
X = a ;
X = b.

?- (true -> X=a); X=b.
X = a.

The first query has two answers but the second one has only one. What is the cause for the different behaviour?

P.S.: I know the difference, but I have not found a SO question that deals with this quite confusing phenomenon. So why not have it documented this way?

2 Answers

Concerning tracability of disjunction. These two queries are structurally different:

trace, (true -> X=a); X=b.

trace, ((true -> X=a); X=b).

You can use write_canonical/1 to see the difference:

?- write_canonical((trace, (true -> X=a); X=b)), nl.
;(','(trace,->(true,=(A,a))),=(A,b))
true.

?- write_canonical((trace, ((true -> X=a); X=b))), nl.
','(trace,;(->(true,=(A,a)),=(A,b)))
true.

And they also behave differently:

?- trace, (true -> X=a); X=b.
   Call: (9) true ? creep
   Exit: (9) true ? creep
   Call: (9) _428=a ? creep
   Exit: (9) a=a ? creep
X = a 
   Call: (9) _428=b ? creep
   Exit: (9) b=b ? creep
X = b.

[trace]  ?- trace, ((true -> X=a); X=b).
   Call: (9) true ? creep
   Exit: (9) true ? creep
   Call: (9) _706=a ? creep
   Exit: (9) a=a ? creep
X = a.

Only the second query would test (true -> X=a); X=b.

First let's address the initial predicate: the formatting suggests a nested if-then-else, but the parenthesis around the second body group the first true into the else branch:

?- listing(p).
p(A, B) :-
    (   cond1(A, B)
    ->  q(A)
    ;   true,
        (   cond2(A, B)
        ->  q(B)
        )
    ;   true
    ).

That's how we end up with the queries starting with true, .... The second problem here is that the use of ; is overloaded: a term of the form G1; G2 is interpreted as a disjunction only if G1 is not of the form (Cond -> Goal). Otherwise it is interpreted as if-then-else. Let's look at the different cases:

  • (true, true -> X=a); X=b interprets ; as a disjunction because the outermost functor of the left hand side is the conjunction ,. Prolog reports the answer subsitution for each branch.
  • (true -> X=a, true); X=b: is a disjunction for the same reason
  • (true -> X=a); X=b: is an if-then-else because the outermost functor of the left hand side is the if-then operator ->. Prolog only reports the answer substitution for the true branch.

Interestingly, when we put the condition into a variable, this seems to not work anymore (on SWI 8):

?- G1 = (true -> (X = a)), (G1 ; X=b).
G1 =  (true->a=a),
X = a ;
G1 =  (true->b=a),
X = b.

The same goes happens when I wrap G1 into a call/1:

?- G1 = (true -> (X = a)), (call(G1) ; X=b).
G1 =  (true->a=a),
X = a ;
G1 =  (true->b=a),
X = b.

If I read the answer to an earlier question correctly, the first one should be different.

I would assume that the different tracing behaviour is that the breakpoints interfere with the detection of if-then-else. My mistake during tracing was to hit enter to creep but did not realize I needed to enter ; when the actual answer was reported.

Related