Lazy lists in Prolog?

后端 未结 6 1666
情深已故
情深已故 2020-11-28 11:45

Is it possible to have lazy lists in Prolog? Something like the following:

ones([1 | Y]) :- ones(Y).

Although this obviously doesn\'t work

6条回答
  •  遥遥无期
    2020-11-28 12:40

    Here's a slightly different take on lazy lists, which uses suspensions. It's written in ECLiPSe, but you should be able to replicate it in other flavours of Prolog.

    It uses an attributed variable to record the current length of the lazy list, and constructs new members of the list when the lower bound of the variable's domain is raised.

    I assume that the predicate that is executed to construct list members has arity 3, and the three arguments are: in-state, out-state, and result. It's easy to adapt the example to general goals, though.

    I also used a "store", which is a non-logical hash storage, in order to quickly retrieve the n-th element of the list by avoiding to iterate over the list. Using a store is not essential, but iterating over a long list again and again can be expensive.

    Here's the predicate that makes the lazy list:

    :- lib(ic). % load IC library (constraints over intervals)
    
    % make new lazy list
    % lazy_list(-,-,-,++,++)
    lazy_list(List, Store, E, Pred, InitState) :-
        store_create(Store),
        E #>= 0,
        suspend(generate_nth_el(E, 0, List, Store, Pred, InitState), 3, E->ic:min).
    

    List is the new list, Store is a handle of a store, Pred the functor of the predicate that generates the list members, InitState the initial state, and E the variable that is used to trigger the list creation.

    New list members are created when the lower bound of E is raised. In that case, generate_nth_el/6 is woken:

    generate_nth_el(E, Last, List, Store, Pred, LastState) :-
        This is Last+1,
        List = [NextVal|Tail],
        Goal =.. [Pred, LastState, NextState, NextVal],
        call(Goal),  % create next element
        store_set(Store, This, NextVal), % add next element to store
        get_min(E, MinE),
        (
            This == MinE
        ->
            % when reached the lower bound, suspend again
            suspend(generate_nth_el(E, This, Tail, Store, Pred, NextState), 3, E->ic:min)
        ;
            % else continue with extending the list
            generate_nth_el(E, This, Tail, Store, Pred, NextState)
        ).
    

    The predicate generate_nth_el/6 is purely for internal use, and should not be called by the user.

    Finally, here's a predicate to retrieve the n-th element:

    % nth_el(++,+,++,-)
    nth_el(N, E, Store, V) :-
        N > 0,
        E #>= N,                % force creation of new elements
        store_get(Store, N, V). % get nth element from store.
    

    It adds a constraint that E must be at least as large as N. If this increases the lower bound, the list is extended. The n-th element is then retrieved from the store.

    As an example, here's a version of the fibonacci number predicate with in- and out-states as used in the code above:

    fib((0,0), (0,1), 0) :- !.
    fib(StateIn, StateOut, B) :-
        StateIn  = (A, B),
        StateOut = (B, C),
        C is A+B.
    

    And here's how it works:

    ?- lazy_list(List, Store, E, fib, (0,0)),
       nth_el(5, E, Store, F5),
       nth_el(3, E, Store, F3),
       nth_el(10, E, Store, F10).
    List = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34|_1179]
    Store = 'STORE'(16'8ee279a0)
    E = E{10 .. 1.0Inf}
    F5 = 3
    F3 = 1
    F10 = 34
    There is 1 delayed goal.
    Yes (0.00s cpu)
    

    Note, though, that lazy lists are often used in other languages to achieve behaviour that in Prolog can be implemented through simple backtracking.

提交回复
热议问题