slago
slago

Reputation: 5519

Unexpected result for predicate nb_setarg/3

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

Answers (4)

slago
slago

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

David Tonhofer
David Tonhofer

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 the nb_* 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

David Tonhofer
David Tonhofer

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 of Cond for which Action is false. The use of double negation implies that forall/2 does not change any variable bindings. It proves a relation. The forall/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

Isabelle Newbie
Isabelle Newbie

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

Related Questions