% Illustrating difference lists
% Author: Frank Pfenning
% Oct 2006, following Sterling & Shapiro, The Art of Prolog

% support for polymorphic type checking
:- op(1200, xfx, ':').
:- discontiguous([(:)/2]).
:- dynamic([(:)/2]).

% support for queues
:- op(200, xfy, '\\').
:- dynamic([queue/1,q/2]).
:- dynamic([reverse_/2, rev/2]).

list : [type] -> type.
[] : [] -> list(_A).
'.' : [A, list(A)] -> list(A).

inst : [type] -> type.
enq : [A] -> inst(A).
deq : [A] -> inst(A).
queue : [list(inst(_A))] -> o.

qu : [type] -> type.
(\) : [list(A),list(A)] -> qu(A).
q : [list(inst(A)),qu(A)] -> o.

/*
This first version of queues allows ``borrowing''
items from the future, that is, we can remove
virtual elements from the queue and only later fill it.
?- queue([deq(X),enq(1)]). succeeds with X = 1.
*/
queue(Is) :- q(Is, B\B).

q([enq(X)|Is], F\[X|B]) :- q(Is, F\B).
q([deq(X)|Is], [X|F]\B) :- q(Is, F\B).
q([], []\[]).

/* to guard against negative queues replace middle clause by
q([deq(X)|Is], F\B) :- F = [] -> fail ; F = [X|F1], q(Is, F1\B).
*/

/*
This second version prevents time travel
by carrying queue size.  Time travel can also be
avoided as indicated above.
?- queue([deq(X),enq(1)]).   fails
*/
queue2(Is) :- q2(Is, Q\Q, 0).

q2([enq(X)|Is], F\[X|B], N) :- N1 is N+1, q2(Is, F\B, N1).
q2([deq(X)|Is], [X|F]\B, N) :- N > 0, N1 is N-1, q2(Is, F\B, N1).
q2([], []\[], 0).

/*
This strawman version is extremely
inefficient because of repeated appends
*/
queue0(Is) :- q0(Is, []).
q0([enq(X)|Is], Q) :- append(Q, [X], Q2), q0(Is, Q2).
q0([deq(X)|Is], [X|Q]) :- q0(Is, Q).
q0([], []).

/*
This strawman version is a translation
of functional queues into Prolog, not as
elegant as the difference list version above.
*/
queue1(Is) :- q1(Is, [], []).
q1([enq(X)|Is], F, B) :- q1(Is, F, [X|B]).
q1([deq(X)|Is], [X|F], B) :- q1(Is, F, B).
q1([deq(X)|Is], [], B) :-
	reverse(B, [X|F]),
	q1(Is, F, []).
q1([], [], []).

reverse_ : [list(A),list(A)] -> o.
rev : [list(A),qu(A)] -> o.

reverse_(Xs, Ys) :- rev(Xs, Ys\[]).
rev([X|Xs], Ys\Zs) :- rev(Xs, Ys\[X|Zs]).
rev([], Ys\Ys).

/*
Naive version, often used for benchmarking purposes.
*/
naive_reverse([X|Xs], Zs) :-
        naive_reverse(Xs, Ys),
	append(Ys, [X], Zs).
naive_reverse([], []).

% quicksort not type-checked: need to extend checker
% to allow =< and > and !

quicksort(Xs, Ys) :-
	qsort(Xs, Ys\[]).

qsort([], Ys\Ys).
qsort([X0|Xs], Ys\Zs) :-
	partition(Xs, X0, Ls, Gs),
	qsort(Gs, Ys2\Zs),
	qsort(Ls, Ys\[X0|Ys2]).

partition([], _, [], []).
partition([X|Xs], X0, [X|Ls], Gs) :-
	X =< X0, partition(Xs, X0, Ls, Gs).
partition([X|Xs], X0, Ls, [X|Gs]) :-
	X > X0, partition(Xs, X0, Ls, Gs).
