Findall/3 and overlapping use of variable names

I remember someone on Stack Overflow raising this interesting edge case:

?- findall(X,member(X,[1,2,3]),X).
X = [1, 2, 3].

The same X appears in two distinct roles, in two distinct context. Namely

findall( { thereis X : member(X,[1,2,3]) } , X )

The question is - should the compiler warn about this? It seems to already perform some magic to cut the link between the first two X and the last X.

Maybe the problem falls under the ‘missing steadfastness’ umbrella.

I don’t think the compiler should bother about this case (too easy :slight_smile: ), because there are so many more difficult points when it come to coroutining (attributed variables), so extensively used today.

From this thread, there should be a ‘lint like’ tool, sorry I haven’t tried it, and crashed :confused: while trying to follow a Google search suggestion


Not really. It is just that findall/3 first uses the first argument and the goal, backtracking over all solutions. In the end, the first argument is unchanged (i.e., still a variable if it was one to begin with) and thus we can reuse it for the second stage (collecting the answers). It is not wrong. It should be considered bad style. Not much different that reusing a variable in imperative coding for two totally different purposes instead of introducing a new variable and assume that the compiler reuses the same location if the scopes do not overlap (and even if not, the damage is really small). So yes, a linter issue.

It is just a file type it doesn’t handle. Only the request ends in a HTTP 500, the server remains happy.


A possibly simpler example:

\+ \+ X=3, X=4 .

The not-not (+\ +\) leaves X uninstantiated, so X=4 succeeds. (\+ \+ Goal is equivalent to calling Goal and then removing all instantiations that were created within Goal; without not-not, X=3, X=4 fails.)

Nice example !

I heard from A. Colmerauer directly, who also had invented the notorious “negation” \+ by CUT (!)

\+(X) :-  X, !, fail.

that he was very proud of this negation. At that time, it was thought negation is impossible because of monotonicity of pure prolog. He cleared the critics by inventing cut (!) As such, still I like cut, but not so much teacher’s attitude to try to hide the cut from the prolog beginner.

Ok, but with

\+ \+ X=3, X=4 .

you know you are supposed to switch from “logical reading” to “operational reading”. \+ (perforce \+ \+) is spiky like a caltrop.



lacks a bit in syntax to make that clear. Definitely merits a linter warning.

More fun with findall/3

?- L=[A,B,C], findall(X,member(X,L),Bag), A=1.
L = [1, B, C],
A = 1,
Bag = [_5876, _5870, _5864].

No A in the Bag because it’s copying the terms or the A inside findall/3 is fresh and not the same A as any A outside.

?- L=[A,B,C], A=1, B=C, findall(X,member(X,L),Bag).
L = [1, C, C],
A = 1,
B = C,
Bag = [1, _11524, _11518].

It’s copying the terms, generating fresh variables.

(Of course, this is completely according to the ISO standard, which actually stipulates the operations that findall/3 should perform in pseudocode)

1 Like

And more fun with findall/3 – you can use it to define non-logical predicates such as:

not(Goal) :- findall(., Goal, []).
var(Var) :- findall(X, (X=a; X=b), [_|_]).

which is why you should be careful about using findall/3 – setof/3 and bagof/3 are safer.

(I think Lee Naish first pointed out this problem with findall/3)

Thanks Peter, I put them in my notes. These are indeed interesting. I think that’s what is called a blivet

Slight correction: The var/1 should of course be:

var(X) :- findall(X, (X=a; X=b), [_,_])

1 Like