Search code examples
prologswi-prologside-effects

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).

Solution

  • 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)).