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.