Reputation: 5519
Does anyone know the reason why predicate nb_setarg/3
does not work correctly when used with the predicate forall/3
in the toplevel of the SWI-Prolog interpreter (v. 8.2.1)?
How it works when used in a goal typed in toplevel:
?-
functor(A, array, 5),
forall(arg(Index, A, _),
nb_setarg(Index, A, 0)).
A = array(_26341340, _26341342, _26341344, _26341346, _26341348).
How it works when used in a rule:
new_array(A,N) :-
functor(A, array, N),
forall(
arg(Index, A, _),
nb_setarg(Index, A, 0)).
Then:
?-
new_array(A,5).
A = array(0, 0, 0, 0, 0).
Upvotes: 4
Views: 184
Reputation: 5519
After trying the following queries,I'm suspecting that predicate functor/3
(or its rationalized version compound_name_arity/3
) is the real source of the problem observed with nb_setarg/3
. It seems that it creates a term with fresh variables that "are not in the scope" of the toplevel query (notice that when the names of variables explicitly appear in the queries, the results are as expected).
?- A =.. [array,X1,X2,X3],
forall( arg(I, A, _),
nb_setarg(I, A, 0) ).
A = array(0,0,0)
?- compound_name_arguments(A, array, [X1,X2,X3]),
forall( arg(I, A, _),
nb_setarg(I, A, 0) ).
A = array(0,0,0)
?- compound_name_arity(A, array, 3),
forall( arg(I, A, _),
nb_setarg(I, A, 0) ).
A = array(_20928, _20930, _20932)
?- functor(A, array, 3),
forall( arg(I, A, _),
nb_setarg(I, A, 0) ).
A = array(_22056, _22058, _22060).
Upvotes: 0
Reputation: 15338
Jan Wielemaker says in issue #733 ("Called from forall/2, nb_setarg/3 applied to a constructed compound term in a toplevel goal has no effect (it becomes setarg/3?") that this is a known problem:
The problem boils down to:
?- functor(C, a, 2), \+ \+ (arg(1, C, x), nb_setarg(1, C, foo)). C = a(_52710, _52712).
I.e., if there is an earlier trailed assignment on the target location, backtracking to before this trailed assignment turns the location back into a variable. In this case the
arg(I,Term,_)
unifies the target with the variable in the toplevel query term. As this is older, the target location becomes a trailed reference to the query variable.
nb_setarg/3
is useful, but a can of worms.
...
I'd have to do a lot of research to find [what is going wrong]. Tracking all the trailing and optimization thereof is non-trivial. Basically, do not backtrack to before where you started using
nb_*
and only use thenb_*
predicates on the same location.
So the idea seems to be that the trail (i.e. the stack of modifications to be roll-back on backtracking if I understand correctly) contains an assignment (arg(1, C, x)
) for exactly the position that is modified with a nb_setarg/3
and you backtrack to before that assignment, your nonbacktrackable assignment is lost.
That makes sense, and seen this way
A = array(_26341340, _26341342, _26341344, _26341346, _26341348).
is actually the correct outcome.
(Switching between logical Prolog and assignment-Prolog makes my head hurt).
I guess this is the way to do it:
A=array(_,_,_,_,_),
forall(
between(1,5,Index),
nb_setarg(Index, A, bar)).
or
functor(A, array, 5),
forall(
between(1,5,Index),
nb_setarg(Index, A, bar)).
Upvotes: 2
Reputation: 15338
I don't know, but the on the toplevel the compound term's modification is rolled back on backtracking for some reason (SWI-Prolog 8.3.14):
Toplevel
?-
functor(A, array, 5),
forall(
arg(Index, A, _),
(format("~q: ~q\n",[Index,A]),
nb_setarg(Index, A, 0),
format("~q: ~q\n",[Index,A]))).
Then we see new array/5
compound terms with fresh variables on each passage of forall
1: array(_3228,_4334,_4336,_4338,_4340)
1: array(0 ,_4334,_4336,_4338,_4340)
2: array(_4332,_3228,_4336,_4338,_4340)
2: array(_4332,0, _4336,_4338,_4340)
3: array(_4332,_4334,_3228,_4338,_4340)
3: array(_4332,_4334, 0,_4338,_4340)
4: array(_4332,_4334,_4336,_3228,_4340)
4: array(_4332,_4334,_4336,0, _4340)
5: array(_4332,_4334,_4336,_4338,_3228)
5: array(_4332,_4334,_4336,_4338,0)
A = array(_4332, _4334, _4336, _4338, _4340).
As a rule
new_array(A,N) :-
functor(A, array, N),
forall(
arg(Index, A, _),
(format("~q: ~q\n",[Index,A]),
nb_setarg(Index, A, 0),
format("~q: ~q\n",[Index,A]))).
Then:
?- new_array(A,5).
1: array(_2498,_2500,_2502,_2504,_2506)
1: array(0,_2500,_2502,_2504,_2506)
2: array(0,_2500,_2502,_2504,_2506)
2: array(0,0,_2502,_2504,_2506)
3: array(0,0,_2502,_2504,_2506)
3: array(0,0,0,_2504,_2506)
4: array(0,0,0,_2504,_2506)
4: array(0,0,0,0,_2506)
5: array(0,0,0,0,_2506)
5: array(0,0,0,0,0)
A = array(0, 0, 0, 0, 0).
On the other hand, the implementation is as follows:
forall(Cond, Action) :-
\+ (Cond, \+ Action).
The above is not a good predicate to use as a loop.
However, the behaviour in the "rule setting" seems correct.
The documentation says:
The predicate
forall/2
is implemented as\+ ( Cond, \+ Action)
, i.e., There is no instantiation ofCond
for whichAction
is false. The use of double negation implies thatforall/2
does not change any variable bindings. It proves a relation. Theforall/2
control structure can be used for its side-effects.
Quite so.
There is nothing special in the description of nb_setarg/3
either.
It's as if nb_setarg/3
were working as setarg/3
on the toplevel?
The trace doesn't reveal anything:
^ Call: (13) format("~q: ~q\n", [1, array(_30756, _32086, _32088, _32090, _32092)]) ? creep
1: array(_30756,_32086,_32088,_32090,_32092)
^ Exit: (13) format("~q: ~q\n", [1, array(_30756, _32086, _32088, _32090, _32092)]) ? creep
Call: (13) setarg(1, array(_30756, _32086, _32088, _32090, _32092), 0) ? creep
Exit: (13) setarg(1, array(0, _32086, _32088, _32090, _32092), 0) ? creep
^ Call: (13) format("~q: ~q\n", [1, array(0, _32086, _32088, _32090, _32092)]) ? creep
1: array(0,_32086,_32088,_32090,_32092)
^ Exit: (13) format("~q: ~q\n", [1, array(0, _32086, _32088, _32090, _32092)]) ? creep
Next "forall" passage: we are using a new compound term!
^ Call: (13) format("~q: ~q\n", [2, array(_32084, _30756, _32088, _32090, _32092)]) ? creep
2: array(_32084,_30756,_32088,_32090,_32092)
^ Exit: (13) format("~q: ~q\n", [2, array(_32084, _30756, _32088, _32090, _32092)]) ? creep
Call: (13) setarg(2, array(_32084, _30756, _32088, _32090, _32092), 0) ? creep
Exit: (13) setarg(2, array(_32084, 0, _32088, _32090, _32092), 0) ?
As it is SWI-Prolog related, you may want to ask this on Discourse.
Update
Tried it online in GNU Prolog.
GNU Prolog demands that the index of arg/3
be instantiated and has no nb_setarg/3
(nor a forall/2
??).
But let's try the following in SWI-Prolog:
functor(A, array, 5),
\+ (
between(1,5,Index),arg(Index, A, _),
\+
(format("~q: ~q\n",[Index,A]),
nb_setarg(Index, A, 0),
format("~q: ~q\n",[Index,A]))).
Doesn't work either.
Update: Trying something simpler & pared-down
As expected:
?-
A=p(1,2,3),nb_setarg(1,A,foo).
A = p(foo, 2, 3).
With double negation. Also keeps the non-backtrackably-set value:
?-
A=p(1,2,3),\+ \+ nb_setarg(1,A,foo).
A = p(foo, 2, 3).
Upvotes: 3
Reputation: 9378
I think this might be a bug. But it might not be a bug (just) in forall/2
or nb_setarg/3
. Because this works:
?- A = array(_, _, _, _, _), forall(arg(Index, A, _), nb_setarg(Index, A, 0)).
A = array(0, 0, 0, 0, 0).
while your example doesn't (SWI 7.6.4):
?- functor(A, array, 5), forall(arg(Index, A, _), nb_setarg(Index, A, 0)).
A = array(_2290, _2292, _2294, _2296, _2298).
Upvotes: 4