Foreach bug?

I am puzzled by the following:

t1(L) :-
	foreach(  between(1,5,N),
	          member(N,L)).
t2(L) :-
	foreach(  between(1,5,N),
	          member(r(N),L)).

Query:

1 ?- t1(L).
L = [1, 2, 3, 4, 5|_] .

2 ?- t2(L).
L = [r(_)|_] .

I would expect t2/1 to produce [r(1),r(2),r(3),r(4),r(5)|_] . What am I missing?
It probably has to do with the way terms are being copied in foreach/2, but I am nonetheless puzzled.

Did you compare with a SWI-Prolog version
before 8.3.4, which didn’t use ‘$unbind_template’?

Edit 28.11.2021:
Ok, I get with an older version:

/* Welcome to SWI-Prolog (threaded, 64 bits, version 8.2.4) */
?- foreach(between(1,5,N), member(r(N),L)).
L = [r(1), r(2), r(3), r(4), r(5)|_27718] .

And the newer version:

/* Welcome to SWI-Prolog (threaded, 64 bits, version 8.5.1) */
̀?- foreach(between(1,5,N), member(r(N),L)).
L = [r(N)|_] 

Thanks for comparing the versions, seems a bug to me.

I see. Yes, the current version destructively resets the goal template, so in fact you just use member/2 wit the same physical r(N) term which is in the end reset to a variable.

If you define mem(E,L) :- duplicate_term(E,E2), member(E2,L). it works fine.

Now it is not clear what to do. The current template based approach is not only a lot faster, but it also solved problems resulting from undesirable copying of the goal arguments. Possibly merely documenting this as a limitation is the best option?

1 Like

thanks for explaining the reason :slightly_smiling_face:

A simpler and faster workaround:

mem(E,L) :-
	member(r(E),L).

t3(L) :-
	foreach(  between(1,5,N),
	          mem(N,L)).

I think the best would be to put a note in the documentation saying:

Note: The implementation of foreach/2 does not 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: …

I think the example is very important because it is a very tricky issue that is hard to understand, the above workaround can be used for the example.

3 Likes