Here's a sketch of a stupid setof
that uses other builtins, though not assert
, and not exactly the ones listed by @false in a comment.
We'll use a list accumulator to collect solutions:
stupid_setof(Template, Goal, Set) :-
stupid_setof(Template, Goal, [], Set).
There are two cases to consider: Either the Goal
can enumerate a solution we have not seen so far, or the only ones it can enumerate are already in our accumulator.
First, the case where there are no solutions we haven't seen. In this case we're done.
stupid_setof(Template, Goal, SolutionsSeen, Set) :-
\+ ( call(Goal),
\+ member(Template, SolutionsSeen) ),
!,
sort(SolutionsSeen, Set).
Now for the stupid part. Consider:
foo(a).
foo(b).
foo(c).
?- SolutionsSeen = [], foo(X), \+ member(X, SolutionsSeen), !.
SolutionsSeen = [],
X = a.
?- SolutionsSeen = [a], foo(X), \+ member(X, SolutionsSeen), !.
SolutionsSeen = [a],
X = b.
?- SolutionsSeen = [a, b], foo(X), \+ member(X, SolutionsSeen), !.
SolutionsSeen = [a, b],
X = c.
?- SolutionsSeen = [a, b, c], foo(X), \+ member(X, SolutionsSeen), !.
false.
So given a list of solutions we've seen before, we can force Goal
to backtrack until it gives us one that we haven't seen before. Note that these queries are independent: In each one we have a completely fresh copy of the foo(X)
goal that starts enumerating from a
.
We can do the same thing programmatically by copying the original goal before calling it, forcing it to start a fresh enumeration from a fresh instance of the Goal
. If this finds a new solution, we can add it to our solutions, then repeat with another fresh copy of the goal, forcing it to enumerate yet another new solution, and so on:
stupid_setof(Template, Goal, SolutionsSeen, Set) :-
copy_term(Goal-Template, GoalInstance-Solution),
call(GoalInstance),
\+ member(Solution, SolutionsSeen),
!,
stupid_setof(Template, Goal, [Solution | SolutionsSeen], Set).
If Goal
has N
answers, this will enumerate on the order of N**2
of them and do corresponding linear searches in the solutions list. It will also perform any side effects that Goal
has multiple times.
But it "works":
?- stupid_setof(X, foo(X), Xs).
Xs = [a, b, c].
And, despite all of its stupidity, this is still less stupid than the standard setof/3
if Goal
has no solutions:
:- dynamic bar/1. % no clauses
?- setof(X, bar(X), Set).
false.
?- stupid_setof(X, bar(X), Set).
Set = [].