Predicate control in Prolog

2019-01-25 10:44发布

问题:

Have a curiosity related to Prolog predicate control.

Supposedly I have a predicate f(A,X) and g(B).

f(A,X):- a,b,c, g(X).
g(B):- true.

a - returns true
b - returns true.
c - returns false.
where a,b and c are random predicates.

How can I continue to evaluate g(X) in the predicate f(A,X) if c returns false?

回答1:

If your intention is to define f(A,X) such that g(X) should be evaluated whether or not c fails, then either:

  1. You could encode this using implication (->) and/or disjunction (;), or
  2. f(A,X) doesn't need to be defined in terms of c. This assumes c has no side-effects (e.g., asserting database facts using assert, or printing IO to a stream) which alter the environment and which cannot be undone on failure of c, in which case the first option is preferable.

There are several alternatives for using disjunction, such as:

f(A,X) :- ((a, b, c) ; (a, b)), g(X).

This definition (above) doesn't depend on c at all, but it will always execute c (as long as a and b succeed). The disjunction (;) allows PROLOG to backtrack to try executing a, b again if c failed at all, and to continue onto g(X). Note that this is equivalent to:

f(A,X) :- a, b, c, g(X).
f(A,X) :- a, b, g(X).

In order for PROLOG not to backtrack to evaluate f(A,X) twice because of the second (identical) head predicate f(A,X) for every evaluation, you may choose to place a cut (!), if your implementation supports it, immediately after the c subgoal in the first clause. The cut is placed after c because we don't want the interpreter to commit to that choice of f(A,X) clause if c had failed, instead, we want the interpreter to fail out of this clause and to try the next one, to effectively ignore c and to continue processing g(X).

Also note that this solution relies on a and b having no side-effects, because when c fails, a and b are executed again. If all a, b, and c have side effects, you can try using implication:

f(A,X) :- a, b, (c -> g(X) ; g(X)).

This will also effectively always execute g(X) whether c fails or not, and will not execute a and b again if c fails. This single-clause definition will also not leave a choice-point like the previous suggestion.



回答2:

I guess you could wrap c in ignore/1. Consider e.g.

?- false, writeln('Hello World!').
false.

?- ignore(false), writeln('Hello World!').
Hello World!
true.

But why would you want to continue if c fails? What's the use case?

I tested this code in SWI-Prolog, I'm not sure if other Prologs have false/0 and ignore/1. The latter can be defined like this though:

ignore(Goal) :- Goal, !.
ignore(_).