Help me understand if foreach/2 has a bug, or if I'm confused

?- length(L, 3), foreach(nth1(N,L,N), nth1(N,L,N)).
L = [_, _, _].

% it seems the second foreach argument doesn't bind variables ... but

?- length(L, 3), length(K, 3), foreach(nth1(N,L,N), nth1(N,K,N)).
L = [_, _, _],
K = [1, 2, 3].

% oh .. it does bind variables.

Why doesn’t the first example yield
L = [1, 2, 3]
?

The above 2nd example does the same like this:

?- length(L, 3), foreach(between(1,3,N), nth1(N,L,N)).
L = [1, 2, 3].

Only it populates K and not L.

Basically the 1st argument of foreach/2 cannot bind anything,
since it gets completely backtracked over, from the 1st to
the last solution with ever new bindings, and after

the last solution undoing any bindings. Only the 2nd argument
of foreach/2 can “return” something.

1 Like

I agree. I understand why.

Again I understand and I agree.

Here things are murky. This can’t be backtracking (as I understand), because we aren’t undoing the bindings we repeatedly update in the second term. So it’s some other “undoing”.
But it’s not even “undo what we did here”, which would work, it’s more like “reset” this variable.
But wherever that happens, couldn’t we copy_term the updated variable, reset the local one, then unify it with the copied term to undo what the first term did, but not the second?

Not sure if you want to go through it all, but “does foreach/2 have a bug” is not a new question :smiley:

See here: Foreach bug?

I am quite sure there are also older discussions on the same topic.

The docs say,

The actual implementation uses findall/3 on a template created from the variables shared between Generator and Goal. Subsequently, it uses every instance of this template to instantiate Goal, call Goal and undo only the instantiation of the template and not other instantiations created by running Goal.

You can see this “variables shared” if you try these queries:

?- length(L, 3), length(K, 3), L = K, foreach(nth1(N,L,N),nth1(N,K,N)).
L = K, K = [_, _, _].

?- length(L, 3), length(K, 3), foreach(nth1(N,L,N),nth1(N,K,N)).
L = [_, _, _],
K = [1, 2, 3].

?- length(L, 3), length(K, 3), foreach(nth1(N,L,N),nth1(N,K,N)), L = K.
L = K, K = [1, 2, 3].
3 Likes