Recently I revisited your logical reading of forall/2 and foreach/2 and found some interesting conclusions.
-
The derivation of the forall/2
The SWI-Prolog manual said that
The predicate forall/2 is implemented as
\+ ( Cond, \+ Action)
but it doesn’t provide a derivation.
In fact, this double negation implementation could be derived from logical equivalence:
forall(p(x), q(x,y)) <=> ∀x.∃y.(p(x) → q(x,y)) <=> ∀x.∃y.(¬ p(x) ∨ q(x,y)) <=> ∀x.(¬ p(x) ∨ ∃y.q(x,y)) % reverse rule of prenex normal form. <=> ¬ ¬ ∀x.(¬ p(x) ∨ ∃y.q(x,y)) <=> ¬ ∃x.¬ (¬ p(x) ∨ ∃y.q(x,y)) <=> ¬ ∃x.(p(x) ∧ ¬ ∃y.q(x,y))
Indeed this result formula
¬ ∃x.(p(x) ∧ ¬ ∃y.q(x,y))
can be implemented by\+ (p(X), \+ q(X,Y))
in Prolog directly, no matterX
andY
are fresh variables or not.For example (I assume
X
is alway a fresh variable, just considerY
),When
Y
is a fresh variable,?- \+ (p(X), \+ q(X,Y))
OK, it follows the FOL definition
¬ ∃x.(p(x) ∧ ¬ ∃y.q(x,y))
, no problem,When
Y
is a gound value,?- Y=1, \+ (p(X), \+ q(X,Y))
The query follows the FOL definition
¬ ∃x.(p(x) ∧ ¬ q(x,1))
, so the double negation form still works. -
The derivation of the foreach/2
foreach(p(x), q(x, y)) <=> ∃y.∀x.(p(x) → q(x,y)) <=> ∃y.¬ ¬∀x.(p(x) → q(x,y)) <=> ∃y.¬ ∃x.¬ (p(x) → q(x,y)) <=> ∃y.¬ ∃x.(p(x) ∧ ¬ q(x,y))
Note that this result formula
∃y.¬ ∃x.(p(x) ∧ ¬ q(x,y))
can not be implemented by\+ (p(X), \+ q(X,Y))
in general, because someone might pass a fresh variable Y.For example,
suppose foreach/2 was also implemented by
\+ (p(X), \+ q(X,Y))
, the following query would be problematic:?- \+ (p(X), \+ q(X,Y))
Here
Y
is a fresh variable.It means
¬ ∃x.(p(x) ∧ ¬ ∃y.q(x,y))
instead of∃y.¬ ∃x.(p(x) ∧ ¬ q(x,y))
. -
forall/2 and foreach/2 are interchangeable when
Y
is a ground value.The forall/2 version:
?- between(1,3,Y), forall(p(X), q(X,Y)).
The FOL semantics of the query is
∃y.between(1,3,y) ∧ ¬ ∃x.(p(x) ∧ ¬ q(x,y))
This is due to the fact that variables are sharing in Prolog (not shadowing).
The foreach/2 version:
?- between(1,3,Y), foreach(p(X), q(X,Y)).
The FOL semantics of the query is
∃y.between(1,3,y) ∧ ¬ ∃x.(p(x) ∧ ¬ q(x,y))
which is the same as the forall/2 version.
-
forall/2 is more efficient than foreach/2 when they are interchangeable.
For example,
repeat(N, Goal) :- forall(between(1,N,_), Goal). q(2,y). q(3,y). ?- time(repeat(100000, (Y=y, forall(between(1,3,X), q(X,Y))))). % 12 inferences, 0.000 CPU in 0.000 seconds (?% CPU, Infinite Lips) false. ?- time(repeat(100000, (Y=y, foreach(between(1,3,X), q(X,Y))))). % 70,214 inferences, 0.016 CPU in 0.027 seconds (58% CPU, 4500869 Lips) false.
This is due to that
forall/2 will short-circuit once
q(1,y)
fail, i.e. it doesn’t tryq(2,y)
andq(3,y)
, whereasforeach/2 must generate all the X Y pairs.
?- Y=y, findall(q(X,Y), between(1,3,X), Goals). Y = y, Goals = [q(1,y),q(2,y),q(3,y)].