As noted in another StackOverflow answer that I can't seem to find anymore, this pattern emerges frequently in practical Prolog code:
pred(X) :-
guard(X),
...
pred(X) :-
\+ guard(X),
...
and many people try to condense this to
pred(X) :-
(guard(X) ->
...
;
...).
However as we all know, the arrow structure destroys choice points and isn't logical.
In Ulrich Neumerkel's and Stefan Kral's Indexing dif/2, a predicate called if_/3
is proposed that is monotonic and logical, however in the paper they mention another construct which caught my eye: *->
.
The *->
construct functions exactly like the unsugared guard clause example above, and thus it seems perfect for my uses as I don't want to have a reified condition which is required by if_/3
and I don't care about extra choice points that much. If I'm not mistaken (edit: I am), it offers the same semantics as if_/3
but without the requirement of adding the "reification" to the condition predicate.
However in the SWI documentation for it, it claims that "this construct is rarely used," which seems weird to me. *->
seems to me like it's strictly better than ->
when you're trying to do pure logical programming. Is there any reason to avoid this structure, or is there an even better alternative to the entire guard clause / negated guard clause pattern?
Let's try it out! The pattern you give is:
pred(X) :- ( guard(X) -> ... ; ... ).
I now use (*->)/2
and fill out the "..." as follows:
pred(X) :- ( guard(X) *-> false ; true ).
Further, as guard/1
, I define the obviously pure predicate:
guard(a).
Now, let's ask pred/1
the most general query: Are there any solutions at all?
?- pred(X). false.
So, according to the predicate, there is no term X
such that pred(X)
is true.
But that's wrong, because there is in fact such a term:
?- pred(b). true.
In fact, pred/1
has infinitely many solutions. In such a situation, is it acceptable that the predicate states there are none at all? Sure thing, because the answer was computed extremely efficiently, is it not so?
We conclude that (*->)/2
shares an important drawback of (->)/2
: It may incorrectly commit to one of the branches in cases where a different branch would be applicable if only the variables that occur in the condition were further instantiated. A predicate that depends on the instantiation of its arguments in such a way can never be pure, because it counteracts the monotonic reasoning we expect to be applicable to pure logic programs. In particular, from a logical perspective, since pred(b)
holds, we expect that pred(X)
, which is a generalization of pred(b)
, must not fail. Once this property breaks, you can no longer apply declarative debugging and other important approaches that let you more easily understand, reason about and manage Prolog programs, and which constitute a major attraction of declarative programming in the first place.
The question you mentioned is probably What uses does if_3/
have?.