mthom / scryer-prolog

A modern Prolog implementation written mostly in Rust.
BSD 3-Clause "New" or "Revised" License
2.01k stars 117 forks source link

How to use `run_query` in streaming mode/limiting number of matches? #2394

Open lambdaofgod opened 5 months ago

lambdaofgod commented 5 months ago

I've written basic bindings for Python in my project using lib_machine::Machine's run_query.

For queries that return small number of matches it works fine, but when I run a query that returns large or infinite numer of matches the program seems to enter infinite loop.

Is there a way to limit number of matches, or even better, is there a way to explicitly return a stream of matches? A stream of matches or something that allows for suspended computation seems perfect as it naturally fits returning a generator in Python.

Looking at code I've also seen a server, but I don't know how to run it - maybe this would be a better approach than calling Rust code, what do you think?

bakaq commented 5 months ago

I don't think Scryer currently has an interface for getting this (what in Rust you would call an iterator). It was postponed from the initial pull request because it wasn't high priority for a lot of use cases. Related: https://github.com/mthom/scryer-prolog/pull/1880#issuecomment-1664883911 https://github.com/mthom/scryer-prolog/pull/1880#issuecomment-1765078753.

One thing you can maybe do is to limit or stream the queries from inside Prolog, although this is not as performant or ergonomic as simply having a run_query() that returns an iterator.

:- use_module(library(iso_ext)).
:- use_module(library(lists)).
:- use_module(library(clpz)).

all_lists_infinite(L) :-
    length(L, _).

all_lists_limited_clpz(L, N) :-
    #Len #>= 0,
    #Len #=< #N,
    label([Len]),
    length(L, Len).

% Very low level (and ugly in my opinion), but more general because
% the predicate that you are limiting doesn't need to be parametrized.
all_lists_limited_bb(L, N) :-
    bb_put(all_lists_count, 0),
    length(L, _),
    bb_get(all_lists_count, Count),
    (   Count > N ->
        % A cut to stop looking for answers
        !, false
    ;   Count1 is Count + 1,
        bb_put(all_lists_count, Count1)
    ).

You could use similar techniques to skip the first N answers, so that you could make an iterator with this, although it would get much slower to get the next answer the further along you go because in the general case you would still need to compute all of the previous answers.

triska commented 5 months ago

@lucksus: This could be an interesting candidate for a generalization of the existing interface, and I would greatly appreciate if could take it into account! Thank you a lot!

UWN commented 5 months ago

Using these bb_ primitives is the entry to non-reentrant code. Use call_nth/2 of library iso_ext in its stead.