I’ve recently been thinking about the following question about the semantics of Prolog:
Given some Prolog goal G
and SLD resolution, what do we know about the conjunction G, false
?
I’ve come to ask myself this question while experimenting with static analysis for my Prolog code. I couldn’t find a clear answer in the literature, although I didn’t do a very thorough search. Are there any suggested references about static analysis / abstract interpretation for Prolog?
Anyway, here’s where I currently stand on this subject after doing some reading and some experimenting:
A purely logical reading of ,/2 as a conjunction would suggest that G,false
fails for any goal G
(1).
But we can easily produce a counter example to show that (1) needs some refinement:
?- [user].
% test that (G, false) fails regardless of G
|: test_hyp1(G) :- \+ (G, false).
|:
|: ^D% user://2 compiled 0.00 sec, 1 clauses
true.
?- test_hyp1(true). % holds for true/0.
true.
?- test_hyp1(false). % holds for false/0.
true.
?- test_hyp1(throw(ball)). % doesn't hold for throw/1!
ERROR: Unhandled exception: Unknown message: ball
?- [user].
|: loop :- loop;true.
|:
|: ^D% user://3 compiled 0.00 sec, 1 clauses
true.
?- test_hyp1(loop). % doesn't hold for loop/0!
% ...infinite loop...
^CAction (h for help) ? abort
% Execution Aborted
We can generalize from the above examples the following characterizations of G,false
:
- If
G
fails, thenG,false
fails. (2) - If
G
throws, thenG,false
throws. (3) - If
G
doesn’t terminate, thenG,false
doesn’t terminate. (4)
And what do we know about G,false
in the case that G
succeeds?
The logical reading could still be salvaged if it were the case that if G
succeeds, then G,false
fails (5). Assuming (5) would entail, along with (2), that if G;true
succeeds, then G,false
fails (6).
But we can show that (6) does not in fact hold:
?- [user].
|: % For every goal G and inference limit L,
|: % if (G;true) can be proven in L inferences
|: % then \+ (G,false) can be proven in L + 1024 inferences
|: test_hyp6(L, G) :-
|: ( call_with_inference_limit(\+ \+ (G;true), L, R), R \== inference_limit_exceeded
|: -> plus(L, 1024, L2), % just to be on the safe side
|: call_with_inference_limit(\+ (G,false), L2, R2), R2 \== inference_limit_exceeded
|: ; true
|: ).
|: ^D% user://6 compiled 0.01 sec, 1 clauses
true.
% holds for true/0, false/0, !/0, and loop/0.
?- maplist(test_hyp6(2048), [true, false, !, loop]).
true.
?- test_hyp6(2048, repeat). % doesn't hold for repeat/0!
false.
?- test_hyp6(2048, length(_, _)). % doesn't hold for length(_, _)!
false.
We can see that G;true
does not guarantee \+ (G,false)
and (5) does not hold.
A more explicit example:
?- [user].
|: pool :- true;pool. % the opposite of loop/0, equivalent to repeat/0
|:
|: ^D% user://9 compiled 0.00 sec, 1 clauses
true.
?- pool.
true .
?- pool;true.
true .
?- \+ (pool,false).
% ...infinite loop...
^CAction (h for help) ? abort
% Execution Aborted
In general, G,false
may, for some G
s, enter an infinite loop even when G
succeeds, by virtue of backtracking.
The key characteristic of this class of goals is that, like pool/0 above and the classic definition of repeat/0, they always succeed with a choice point that always leads to a goal that is a variant of the original goal. Such goals yield infinite choice points upon backtracking.
To summarize, the semantics of G,false
as a function of the semantics of G
is given by:
- If
G
throwsBall
,G,false
throwsBall
. - If
G
doesn’t terminate,G,false
doesn’t terminate. - If
G
yields infinite choice points upon backtracking,G,false
doesn’t terminate. - Otherwise,
G,false
fails.
Now the next interesting question arises - how can we know in which of these branches a given goal G
falls or at least in which branches G
doesn’t fall?..