Say I want to find sum of all solutions to a predicate, I just can use
findall(L, find(L), Sols),
and just sum members of Sols.
But
Needing to make a thread-safe solution in SWI Prolog, I came with this:
find_n( N, Solution, Goal, Solutions ) :-
thread_self( Thread_id ),
atomic_list_concat( [counter, Thread_id], Counter_flag_key ),
flag( Counter_flag_key, _, 0 ),
findall( Solution, (
call( Goal ),
flag( Counter_flag_key, X, X ),
X1 is X + 1,
flag( Counter_flag_key, _, X1 ),
( X1 >= N -> !; true )
), Solutions ).
The Logtalk threaded engines API or the SWI-Prolog coroutining engines API, both added recently, provide an alternative solution. For example, using Logtalk threaded engines:
find_at_most(N, Template, Goal, List) :-
threaded_engine_create(Template, Goal, Engine),
collect_at_most(N, Engine, List0),
threaded_engine_destroy(Engine),
List = List0.
collect_at_most(N, Engine, [X| Xs]) :-
N > 0,
threaded_engine_next(Engine, X),
!,
M is N - 1,
collect_at_most(M, Engine, Xs).
collect_at_most(_, _, []).
The threaded_engine_create/3
predicate computes the first solution and then suspends waiting for it to be retrieved by the threaded_engine_next/2
predicate. When a solution is retrieved, the engine starts computing the next solution, again suspending until it's consumed (when a call to the threaded_engine_next/2
predicate happens before a solution is ready, this predicate simply blocks waiting for it). Thus, at most N
solutions are computed.
The solution using SWI-Prolog coroutining engines is almost identical but without the blocking semantics (in the threaded engines version) and expected to be more efficient unless the implicit overhead of threads is offset by exploiting concurrency (elsewhere) in the application:
find_at_most(N, Template, Goal, List) :-
engine_create(Template, Goal, Engine),
collect_at_most(N, Engine, List0),
engine_destroy(Engine),
List = List0.
collect_at_most(N, Engine, [X| Xs]) :-
N > 0,
engine_next(Engine, X),
!,
M is N - 1,
collect_at_most(M, Engine, Xs).
collect_at_most(_, _, []).
Both APIs also support reified versions of the _next/2
predicates, and thus alternative implementations of the find_at_most/4
predicate, if you want to get rid of the cut in the auxiliary predicate.
Last, but not the least, the solution presented here originated from Paul Tarau work and papers on engines.
I think you will need to manually implement the uniqueness condition that is implied in findall, i.e., you will need to aggregate solutions and for each new one check that it hasn't yet been picked before.
Here is an example of how this can work:
%% an example predicate with infinite solutions
find(0).
find(X) :- find(X1), X is X1+1.
%% myfindall(+Num) holds when there are at least Num different solutions for find
myfindall(Num) :-
length(L, Num),
mfa_aux(L, [], All),
%% now do something with All, e.g., sum it.
writeln(All).
mfa_aux([], All, All).
mfa_aux([H|T], All, Rtv) :-
find(H),
not(member(H, All)), !,
mfa_aux(T, [H|All], Rtv).
%% Test
%% ?- myfindall(10).
You can combine findall/3
with limit/2
from the library solution
sequence, inspired from SQL:
limit(+Count, :Goal)
Limit the number of solutions
https://www.swi-prolog.org/pldoc/man?predicate=limit/2
Here is an example run:
?- findall(X, limit(3, (repeat, X = 1)), L).
L = [1, 1, 1].
A couple of Prolog systems have predicates like limit/2
in their repertoire.
SWI-Prolog offers the built-in predicates findnsols/4 and findnsols/5
?- findnsols(5, I, between(1, 12, I), L).
L = [1, 2, 3, 4, 5] ;
L = [6, 7, 8, 9, 10] ;
L = [11, 12].
You may want to wrap the whole call in once/1 to prevent backtracking for further solution groups (e.g. if in the example above you want the 1-5 list to be your ONLY solution).
?- once( findnsols(5, I, between(1, 12, I), L) ).
L = [1, 2, 3, 4, 5].
I've just realized that in B-Prolog it's trivial to do via tabling:
%% an example predicate with infinite solutions
%% from ChristianF answer.
find(0).
find(X) :- find(X1), X is X1+1.
:- table find(-).
?- table_cardinality_limit(find/1, 10), findall(X, find(X), L).
L = [0,1,2,3,4,5,6,7,8,9]
yes