4

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?

lambda.xy.x
  • 4,918
  • 24
  • 35
  • Interesting behaviour: `?- (true -> X=a); X=b.` i get `X = a.` Instead: `trace, (true -> X=a); X=b.` I get `X = a ; X = b`. I'm con SWISH online. – damianodamiano Jun 06 '19 at 21:13
  • This is very interesting. I always thought of `p1 -> p2` as being equivalent to `p1, !, p2`. However, `(true, !, X=a), true); X = b` only yields one solution `X=a`, whereas `((true -> X=a), true); X=b.` yields two. I'm actually surprised it yields two. – lurker Jun 06 '19 at 21:14
  • @damianodamiano interesting. Just in gprolog, `trace, (true -> X=a); X=b.` yields two solutions, but `trace, (true, !, X=a) ; X=b` yields just one (`X=a`) which is the behavior I would have expected from `->` as well. – lurker Jun 06 '19 at 21:17
  • Guy Coder: I wanted other people to earn the points and add my solution in the end. I can put it in now, if you like. – lambda.xy.x Jun 06 '19 at 21:51
  • See the same issue here : https://stackoverflow.com/q/56111688/3768871 – OmG Jun 06 '19 at 22:11
  • @OmG: that answer is about constraints and if-then-else. This effect is just plain prolog and about the distinction between disjunction `;` and if-then-else `-> ;`. – lambda.xy.x Jun 06 '19 at 22:17
  • `I wanted other people to earn the points and add my solution in the end.` I use to think that way also. There is nothing wrong with posting a question that like this that should be made known and is not easily found and then quickly posting an answer. The problem comes when you post the answer and the quickly accept your own answer. By posting the answer right away, others will see the answer and then learn from it, mostly people new to the topic, and by not accepting your answer you are following the normal practice at SO. :) – Guy Coder Jun 07 '19 at 09:18
  • If others want to add their own answer they can if they see that the answer is not accepted. It is not about the points it is about helping others learn. – Guy Coder Jun 07 '19 at 09:20

2 Answers2

3

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.

  • I also asked in the SWI forum and it seems to have been a user error on my side as well - I hit enter to creep on but didn't enter the `;` to get the other answer. – lambda.xy.x Jun 08 '19 at 12:52
  • I also used `trace.` to go into tracing mode and entered the query separately. – lambda.xy.x Jun 08 '19 at 12:53
0

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.

lambda.xy.x
  • 4,918
  • 24
  • 35
  • I'm not sure I agree with the interpretation that `;/2` is overloaded. In the case of `g1 -> g2 ; g3`, `g1 -> g2` by definition eliminates the choice point at `g1`. For `g3` to be called, there would need to be a choice point which takes the second argument of `;/2` once the first argument fails. That's also why I'm surprised `(true -> X=a, true); X=b` yields 2 results as I mentioned in my comments to the question. – lurker Jun 06 '19 at 22:39
  • I had the idea from a cryptic remark in the [SWI Documentation for ->](http://www.swi-prolog.org/pldoc/man?predicate=-%3E/2). It seems the term really must be of the form `; ( -> (g1, g2), g3)`. Perhaps I'm wrong but isn't the parenthesis of the term `( (true -> X=a), true )`? That would make `,` the child functor of `;` and it's not the definition of if-then-else we're supposed to follow. – lambda.xy.x Jun 06 '19 at 22:50
  • Yeah you're right. `((true -> X=a), true) ; X=b` isn't in the `if-then-else` form. But the behavior of `->/2` should be any different, so it should get rid of the choice point at the first `true` (or it should I would think). That's why I'm surprised `X=b` is shown as a solution. – lurker Jun 07 '19 at 00:59
  • Always use `listing/1` to set the round brackets right. – false Jun 07 '19 at 09:22
  • `I would assume that the different tracing behaviour is that the breakpoints interfere with the detection of if-then-else.` If you ask this at [SWI-Prolog discourse](https://swi-prolog.discourse.group/) there is a good chance that Jan will reply. – Guy Coder Jun 07 '19 at 09:23
  • @false i used write_canonical/1 but didn't think of listing - write_canonical does not print the parenthesis in this case. – lambda.xy.x Jun 07 '19 at 11:13