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?
If your intention is to define f(A,X)
such that g(X)
should be evaluated whether or not c
fails, then either:
- You could encode this using implication (
->
) and/or disjunction (;
), or
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.
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(_).