Probable bug in arg/3 when called from foreach/2

arg/3 does not seem to be able to set deep terms when invoked in the foreach/2 context. I’ve discovered this in threaded 64bit version 8.4.2 running under Linux and confirmed that in threaded 64bit version 9.2.9.1 running under Windows OS. Below follows the code with explanation in the final comment:


data(1,1,0).
data(2,0,1).
data(3,4,4).
data(5,0,0).

make_array1(A):-
  functor(A, array,10),
  foreach(data(Nr,X,Y),(format("x=~q y=~q\n",[X,Y]),
                        arg(Nr,A,X:Y)) ).

bug1:-make_array1(A),writeln(a=A).


make_array2(A):-
  functor(A, array,10),
  foreach(data(Nr,X,Y),(format("x=~q y=~q\n",[X,Y]),
                        Z=X:Y,
                        arg(Nr,A,Z)) ).  

bug2:-make_array2(B),writeln(b=B).


make_array3(A):-
  functor(A, array,10),
  foreach(data(Nr,X,Y),(format("x=~q y=~q\n",[X,Y]),
                        arg(Nr,A,X)) ).
nobug0:-make_array3(C),writeln(c=C).


make_array4(A):-
  functor(A, array,10),
  foreach(data(Nr,X,Y),(format("x=~q y=~q\n",[X,Y]),
                        arg(Nr,A,Z),
                        Z=X:Y )).  

bug3:-make_array4(D),writeln(d=D).


make_array5(A):-
  functor(A, array,10),
  findall(n(N,X,Y),data(N,X,Y),L),
  maplist({A}/[E]>>(E=n(N,X,Y),arg(N,A,X:Y)),L).

nobug1:-make_array5(E),writeln(e=E).


make_array6(A):-
  functor(A, array,10),
  findall(n(N,X,Y),data(N,X,Y),L),
  do_make_array(L,A).



do_make_array([],_).
do_make_array([n(N,X,Y)|L],A):-arg(N,A,X:Y),do_make_array(L,A).

nobug2:-make_array6(F),writeln(f=F).

/* bug1, bug2 and bug3 only creates the template and does not fill values, i.e. the 
   output looks like so:

?- bug1.
x=1 y=0
x=0 y=1
x=4 y=4
x=0 y=0
a=array(_31412:_31414,_31412:_31414,_31412:_31414,_31394,_31412:_31414,_31398,_31400,_31402,_31404,_31406)

When filling just one coordinate using nobug0, it does enter the data:

?- nobug.
x=1 y=0
x=0 y=1
x=4 y=4
x=0 y=0
c=array(1,0,4,_542,0,_546,_548,_550,_552,_554)

nobug1 and nobug2 work just fine:

?- nobug2.
f=array(1:0,0:1,4:4,_47596,0:0,_47600,_47602,_47604,_47606,_47608)
true.

*/

There is a comment in the code of foreach/2:

%   Note that SWI-Prolog up to  version   8.3.4  created  copies of Goal
%   using  copy_term/2  for  each  iteration,  this  makes  the  current **
%   implementation unable to properly handle   compound terms (in Goal's
%   arguments) that share variables with the  Generator. As a workaround
%   you can define a goal that does not use compound terms, like in this
%   example:

So for a compound like X:Y to work, you need to code a
satelite predicate, that has no compounds in its arguments:

bar(Nr,A,X,Y) :- arg(Nr,A,X:Y).

Now you can do:

?- functor(A,foo,10), foreach(data(Nr,X,Y),bar(Nr,A,X,Y)).
A = foo(1:0, 0:1, 4:4, _, 0:0, _, _, _, _, _).
2 Likes