Ленивые списки в Прологе?
Возможно ли иметь ленивые списки в Прологе? Что-то вроде следующего:
ones([1 | Y]) :- ones(Y).
Хотя это явно не работает, как написано.
6 ответов
Маркус Триска разместил здесь в открытом доступе некоторый код, который стоит изучить:
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Prolog stream/lazy list demonstration
Written 2005 by Markus Triska (triska@gmx.at)
Public domain code.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
Название документа ( прост, для потоков Пролога) может затруднить поиск документа, но имеет смысл. Цитирую из вышесказанного:
Здесь "поток" используется в смысле "последовательность", "отложенный список", "отложенный список" и т. Д., Как в структуре и интерпретации компьютерных программ, а не в смысле потока ввода-вывода Prolog.
Вот ленивый список чисел Хэмминга в Прологе с использованием "генератора идиомы".
Чем больше я думаю об этом, тем больше мне кажется, что ленивые списки на Haskell - это просто открытые (иначе говоря, "разница") списки Prolog, а corecursion - просто причудливое имя для построения нисходящего списка построения хвостовой рекурсии по модулю cons, Кроме того, Haskell неявно устанавливается один раз в языке, в то время как (не возвращая назад подмножество) 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's 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).
Для целочисленных перечислений, подобных Хаскеллу, мы определяем
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).
Теперь перезапуск с ранее использованного генератора не будет пересчитывать числа, но вместо этого будет повторно извлекать ранее вычисленные элементы последовательности, где это возможно. Это внутреннее общее открытое хранилище хрупко для неправильного использования, то есть неправильного создания экземпляра (правка: его еще не установлена переменная последнего хвостового указателя). Это причина take
строит новое хранилище для своего ответа, вместо того, чтобы выставлять внутреннее.
Да, в Прологе можно создавать ленивые списки. Вот бесконечный, ленивый список тех, кто использует freeze/2
,
ones(L) :-
freeze(L, (L=[1|T],ones(T)) ).
Использование его на верхнем уровне выглядит так:
?- ones(Ones), [A,B,C|_] = Ones.
A = B = C = 1.
Вам также может понравиться пакет list_util (для SWI-Prolog), который содержит несколько ленивых предикатов списка.
Ну, вы можете определить бесконечный список из них (или что-нибудь еще) как:
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-прологе) (:(ленивый список haskell - такая хорошая функция),
Вот немного другой взгляд на ленивые списки, в которых используются приостановки. Он написан на ECLiPSe, но вы должны быть в состоянии повторить его в других вариантах Prolog.
Он использует атрибутивную переменную для записи текущей длины отложенного списка и создает новые члены списка, когда поднимается нижняя граница домена переменной.
Я предполагаю, что предикат, который выполняется для создания членов списка, имеет арность 3, и три аргумента: in-state, out-state и result. Однако легко адаптировать пример к общим целям.
Я также использовал "хранилище", которое является нелогичным хеш-хранилищем, чтобы быстро извлечь 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 может быть реализовано с помощью простого возврата.
% 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]).