Ленивые списки в Прологе?

Возможно ли иметь ленивые списки в Prolog? Что-то вроде следующего:

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

Хотя это явно не работает, как написано.

Ответ 1

Маркус Триска разместил здесь в открытом доступе некоторый код, который стоит изучить:

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Prolog stream/lazy list demonstration

   Written 2005 by Markus Triska ([email protected])
   Public domain code.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

Название документа (прост, для потоков Пролога) может затруднить его поиск, но имеет смысл. Цитирую из вышесказанного:

Здесь "поток" используется в смысле "последовательность", "отложенный список", "отложенный список" и т.д., Как в структуре и интерпретации компьютерных программ, а не в смысле потока ввода-вывода Prolog.

Ответ 2

Здесь перечислены номера Хэмминга ленивого списка в Прологе с использованием "идиомы генератора".

Чем больше я думаю об этом, тем больше мне кажется, что ленивые списки Haskell - это просто открытые (ака "разностные" ) списки Prolog и corecursion просто причудливое имя для построения списка сверху вниз хвостовая рекурсия по модулю минус. Кроме того, Haskell неявно задает один раз язык, тогда как (подмножество non-backtracking) Prolog явно задано один раз.

Методика "привязка к узлу" с помощью мозгов на самом деле представляет собой не что иное, как неловкое внедрение поздней модификации. И все это генератор в Haskell, с памятью хранения универсального посредника доступа. Но в любом случае

Далее реализуются потоки с принудительным потоком (из SICP-сорта), где, если элемент принудительно, все элементы, предшествующие ему в списке, уже принудительно также.

:- dynamic( next/3 ).

%// collect N elements produced by a generator in a row: 
take( 0, Next, Z-Z, Next):- !.
take( N, Next, [A|B]-Z, NextZ):- N>0, !, next( Next, A, Next1),
  N1 is N-1,
  take( N1, Next1, B-Z, NextZ).

%// a "generator" provides specific `next/3` implementation 
next( hamm( A2,B,C3,D,E5,F,[H|G] ), H, hamm(X,U,Y,V,Z,W,G) ):- 
  H is min(A2, min(C3,E5)),
  (   A2 =:= H -> B=[N2|U], X is N2*2 ; (X,U)=(A2,B) ),
  (   C3 =:= H -> D=[N3|V], Y is N3*3 ; (Y,V)=(C3,D) ),
  (   E5 =:= H -> F=[N5|W], Z is N5*5 ; (Z,W)=(E5,F) ).

%// Hamming numbers generator init state:
mkHamm( hamm(1,X,1,X,1,X,X) ).       

%// A calling example: main( +Number)
main(N) :- 
    mkHamm(G), take(20,G,A-[],_),          write(A), nl,  
    take(N-1,G,_,G2), take(2,G2,B-[],_),   write(B), nl.

Простой, а? Для ones просто определим

next( ones, 1, ones).

поскольку там нет (изменить) состояние, а затем называть его, например,

take(N, ones, A-[], _), writeln(A).

Для целых нумераций Haskell мы определяем

next( fromBy(F,B), F, fromBy(F2,B)):- F2 is F+B.

и вызовите take(8, fromBy(3,2), A-[], _), чтобы получить A = [3, 5, 7, 9, 11, 13, 15, 17]. Последовательность Фибоначчи просто

next( fib(A,B), A, fib(B,C)):- C is A+B.

С take(20, fib(0,1), FS-[], _) определяется список из 20 элементов FS чисел Фибоначчи.

Запоминающая последовательность Фибоначчи

mkFibs( fibs([0|L], L) ):- L=[1|_].
next( fibs([A|L], L), A, fibs(L, L2) ):- 
    L=[B|L2], L2=[C|_], (var(C)-> C is A+B ; true).

Теперь перезапуск из ранее использованного генератора не будет пересчитывать числа, но вместо этого будет повторно извлекать ранее вычисленные члены последовательности, если они доступны. Это внутреннее общее хранилище открытого типа является хрупким для неправильного использования, т.е. Неправомерным экземпляром ( edit: его еще не заданной последней переменной указателя хвоста). Это причина для take создания нового хранилища для ответа, вместо того, чтобы подвергать его внутреннему.

Ответ 3

Да, в Прологе возможно иметь ленивые списки. Здесь бесконечный, ленивый список тех, кто использует freeze/2.

ones(L) :-
  freeze(L, (L=[1|T],ones(T)) ).

Использование его на верхнем уровне выглядит следующим образом:

?- ones(Ones), [A,B,C|_] = Ones.
A = B = C = 1.

Вам также может понравиться list_util pack (для SWI-Prolog), который содержит несколько предикатов ленивого списка.

Ответ 4

ну, вы можете определить бесконечный список из них (или что-то еще) как:

H = [1|H].

использование:

4 ?- H=[1|H], [A1|T1] = H, [A2|T2] = T1, T1=T2.
H = [1|**],
A1 = 1,
T1 = [1|**],
A2 = 1,
T2 = [1|**].

Конечно, это не сработает, если вам нужен список простых чисел/фибоначчи/ничего не столь тривиального.

Вы можете написать некоторые предикаты, которые будут эмулировать поведение ленивого списка, но я не думаю, что есть какие-либо библиотеки/стандартизованный способ сделать это (по крайней мере, в swi-prolog) (:( haskell lazy list - это такой приятная функция).

Ответ 5

Здесь немного отличается от ленивых списков, в которых используются приостановки. Он написан в ECLiPSe, но вы должны иметь возможность копировать его в других вариантах Prolog.

Он использует атрибутную переменную для записи текущей длины ленивого списка и строит новые члены списка при повышении нижней границы области переменной.

Я предполагаю, что предикат, который выполняется для построения членов списка, имеет arity 3, а три аргумента: in-state, out-state и result. Однако легко адаптировать пример к общим целям.

Я также использовал "store", который является нелогичным хеш-памятью, чтобы быстро получить n-й элемент списка, избегая итерации по списку. Использование магазина не является существенным, но повторение длинного списка снова и снова может быть дорогостоящим.

Здесь предикат, который делает ленивый список:

:- 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 - это новый список, Store - это дескриптор хранилища, Pred - функтор предиката, который генерирует члены списка, InitState начальное состояние и E переменную, которая используется для запуска создания списка.

Новые члены списка создаются при повышении нижней границы E. В этом случае просчитывается generate_nth_el/6:

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)
    ).

Предикат generate_nth_el/6 предназначен исключительно для внутреннего использования и не должен вызываться пользователем.

Наконец, здесь предикат для извлечения n-го элемента:

% 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.

Он добавляет ограничение, что E должно быть как минимум равно N. Если это увеличивает нижнюю границу, список расширяется. Затем n-й элемент извлекается из хранилища.

В качестве примера здесь приведена версия предиката числа фибоначчи с внутренними и внешними состояниями, используемыми в приведенном выше коде:

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

И вот как это работает:

?- 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)

Заметьте, однако, что ленивые списки часто используются на других языках для достижения поведения, которое в Prolog может быть реализовано с помощью простого обратного отслеживания.

Ответ 6

% A simple generic solution using SWI-Prolog 

% Returns a prefix of a lazy sequence

prefix(Prefix,PrefixLength,LazySequenceName) :-
    apply(LazySequenceName,[LazySequence]),
    length(Prefix,PrefixLength),
    append(Prefix,_,LazySequence).

% Lazy sequence of natural numbers

nat(LazySequence) :- 
    nat(0,LazySequence).
nat(Item,LazySequence) :-
    freeze(LazySequence,
      (LazySequence=[Item|Rest], Next is Item+1, nat(Next,Rest)) ).

% Lazy sequence of Fibonacci numbers

fib(LazySequence) :- 
    fib(1,0,LazySequence).
fib(A,B,LazySequence) :-
    freeze(LazySequence,
       (LazySequence=[C|Rest], C is A+B, fib(B,C,Rest))).

% Examples

test :-
    prefix(N,10,nat), format('Ten first natural numbers: ~w~n',[N]),
    prefix(F,10,fib), format('Ten first Fibonacci numbers: ~w~n',[F]),
    fib(S), nth1(100,S,X), format('The hundredth Fibonacci number: ~w~n',[X]).