mthom / scryer-prolog

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

Feature Request: `call_continuation/1` #2538

Open jjtolton opened 1 week ago

jjtolton commented 1 week ago
:- use_module(library(cont)).

w(X) :-
        write(X),
        nl.

p :-
        reset(q, Term1, Cont), % note, order of args is swapped due to difference in paper's impl vs scryer impl
        w(Term1),
        '$call_continuation'(Cont),
        w(endp).

q :-
        w(a),
        shift(qterm),
        w(b).

?- p.
%@ a
%@ qterm
%@    error(type_error(list,cont(cont:call_continuation([cont_chunk(dir_entry(88587))]))),call_continuation/1).

image

As mentioned in https://github.com/mthom/scryer-prolog/issues/2537#issue-2512093075, the paper is the only source of documentation for the cont library right now, so simply based on this I would assume the current behavior qualifies as an issue.

Edit: fixed code per @hurufu 's observation

Edit1: Changing the code to the type expected by $call_continuation/1 does not work either:

p :-
        reset(q, Term1, Cont), % note, order of args is swapped due to difference in paper's impl vs scryer impl
        w(Term1),
        '$call_continuation'([Cont]), % changed to list
        w(endp).

?- p.
%@ a
%@ qterm
%@ thread 'main' panicked at src/machine/heap.rs:264:17:
%@ to_local_code_ptr crashed with p.i. :/2
%@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Edit2: Per @hurufu 's observation, converted this to a feature request rather than an "issue"

hurufu commented 1 week ago

I've been programming in Prolog for couple years now and I never had to use library(cont), so it is a grey area for me. My feeling is that it is useful mostly for "low-level" Prolog (like described here) and with an advent of attributed variables usefulness of it has declined.

Regarding your question all predicates that start with $ are not supposed to be called by user code and should be used through wrappers. If there is no wrapper then this predicate counts as not-implemented, so I'm not surprised that it doesn't work as expected. The only implemented predicates are shift/1 and reset/3.

hurufu commented 1 week ago

Also I see that Cont is a free variable, and it shouldn't be so, it must be unified with "continuation"

jjtolton commented 1 week ago

Also I see that Cont is a free variable, and it shouldn't be so, it must be unified with "continuation"

Ugh, yeah, you are absolutely right. Fixed the code.

jjtolton commented 1 week ago

I've been programming in Prolog for couple years now and I never had to use library(cont), so it is a grey area for me. My feeling is that it is useful mostly for "low-level" Prolog (like described here) and with an advent of attributed variables usefulness of it has declined.

Regarding your question all predicates that start with $ are not supposed to be called by user code and should be used through wrappers. If there is no wrapper then this predicate counts as not-implemented, so I'm not surprised that it doesn't work as expected. The only implemented predicates are shift/1 and reset/3.

You could be right -- it's hard to say until I understand how to use it better. The paper has some really interesting examples:

yield(Term) :-
        shift(yield(Term)).

init_iterator(Goal,Iterator) :-
        reset(Goal,Cont,YE),
        (   YE = yield(Element) ->
            Iterator = next(Element,Cont)
        ;   
            Iterator = done
        ).

next(next(Element,Cont),Element,Iterator) :-
        init_iterator(call_continuation(Cont),Iterator).
ask(X) :-
        shift(ask(X)).

with_read(Goal) :-
        reset(Goal,Cont,Term),
        (   Term = ask(X) ->
            read(X),
            with_read(call_continuation(Cont))
        ;   
            true
        ).

with_list(L,Goal) :-
        reset(Goal,Cont,Term),
        (   Term = ask(X) ->
            L = [X|T],
            with_list(T,call_continuation(Cont))
        ;   
            true
        ).
transduce(IG,TG) :-
        reset(TG,ContT,TermT),
        transduce_(TermT,ContT,IG).
transduce_(0,_,_).
transduce_(yield(NValue),ContT,IG)) :-
        yield(NValue),
        transduce(IG,call_continuation(ContT)).
transduce_(ask(Value),ContT,IG) :-
        reset(IG,ContI,TermI),
        (   TermI == 0 ->
            true
        ;   
            TermI = yield(Value),
            transduce(call_continuation(ContI),call_continuation(ContT))
        ).

doubler :-
        ask(Value),
        NValue is Value * 2,
        yield(NValue),
        doubler.

?- play(sum(Sum),transduce(fromList([1,2]),doubler)).
%@ Sum = 6.

These are just a few of the patterns mentioned in the paper, some of which I recognized as being quite useful/powerful! I'd be curious to know how much effort it would take to get call_continuation working :thinking:

jjtolton commented 1 week ago

Regarding your question all predicates that start with $ are not supposed to be called by user code and should be used through wrappers. If there is no wrapper then this predicate counts as not-implemented, so I'm not surprised that it doesn't work as expected. The only implemented predicates are shift/1 and reset/3.

Good point, changed to feature request.