%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1997,2000,2003,2005-2006 The University of Melbourne.
% Copyright (C) 2014-2015,2021-2022 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%---------------------------------------------------------------------------%
%
% File: ra_list.m
% Main author: bromage.
% Stability: medium-low
%
% This module implements `random access lists', or ra_lists for short.
% It is very similar to a list data type, and it supports O(1) head/tail/cons
% operations, but it also supports O(log n) lookup and update.
% The representation is a list of perfectly balanced binary trees.
%
% For more details on the implementation:
%
%   Chris Okasaki, "Purely Functional Random-Access Lists".
%   Functional Programming Languages and Computer Architecture,
%   June 1995, pp 86-95.
%
%---------------------------------------------------------------------------%

:- module ra_list.
:- interface.

:- import_module list.

:- type ra_list(T).

%---------------------------------------------------------------------------%
%
% Constructing ra_lists.
%

    % Return an empty random access list.
    %
:- pred init(ra_list(T)::uo) is det.

    % Return a random access list containing only the given item.
    %
:- func singleton(T) = ra_list(T).

    % Return a random access list with the given head and tail.
    %
:- pred cons(T::in, ra_list(T)::in, ra_list(T)::out) is det.

%---------------------------------------------------------------------------%
%
% Deconstructing ra_lists.
%

    % Return the head of the given random access list.
    % Fail if it is empty.
    %
:- pred head(ra_list(T)::in, T::out) is semidet.

    % Return the tail of the given random access list.
    % Fail if it is empty.
    %
:- pred tail(ra_list(T)::in, ra_list(T)::out) is semidet.

    % Return the head and the tail of the given random access list.
    % Fail if it is empty.
    %
:- pred head_tail(ra_list(T)::in, T::out, ra_list(T)::out) is semidet.

%---------------------------------------------------------------------------%
%
% Tests on ra_lists.
%

    % Succeed iff the given random access list is empty.
    %
:- pred is_empty(ra_list(T)::in) is semidet.

    % Succeed iff the given random access list is not empty.
    %
:- pred is_not_empty(ra_list(T)::in) is semidet.

    % Succeed iff the given random access list contains only one item.
    % Return that item.
    %
:- pred is_singleton(ra_list(T)::in, T::out) is semidet.

%---------------------------------------------------------------------------%
%
% Counting items in ra_lists.
%

    % Return the number of items in the given random access list.
    %
:- func length(ra_list(T)) = int.
:- pred length(ra_list(T)::in, int::out) is det.

%---------------------------------------------------------------------------%
%
% Random access on ra_lists.
%

    % Return the item at the given index in the given random access list.
    % The number at the end of the predicate name gives the index of the first
    % element.
    %
    % Fail if the list is not long enough to have an element
    % at the given index.
    %
:- pred index0(ra_list(T)::in, int::in, T::out) is semidet.
:- pred index1(ra_list(T)::in, int::in, T::out) is semidet.

    % Return the item at the given index in the given random access list.
    % The number at the end of the predicate name gives the index of the first
    % element.
    %
    % Fail if the list is not long enough to have an element
    % at the given index.
    %
:- pred det_index0(ra_list(T)::in, int::in, T::out) is det.
:- pred det_index1(ra_list(T)::in, int::in, T::out) is det.

    % Replace the item at the given index in the given random access list.
    % The first element is at index 0.
    %
    % Fail if the list is not long enough to have an element
    % at the given index.
    %
:- pred update(int::in, T::in, ra_list(T)::in, ra_list(T)::out) is semidet.

%---------------------------------------------------------------------------%

    % Append two random access lists.
    %
:- pred append(ra_list(T)::in, ra_list(T)::in, ra_list(T)::out) is det.

    % Drop the given number of initial items from the given random access list.
    %
    % Returns the list unchanged if the number of elements to drop is zero
    % or negative.
    %
    % Fail if the list does not have at least that number of elements.
    %
:- pred drop(int::in, ra_list(T)::in, ra_list(T)::out) is semidet.

%---------------------------------------------------------------------------%

    % Convert a list to a random access list.
    %
:- pred list_to_ra_list(list(T)::in, ra_list(T)::out) is det.

    % Convert a random access list to a plain list.
    %
:- pred ra_list_to_list(ra_list(T)::in, list(T)::out) is det.

%---------------------------------------------------------------------------%

    % map(F, L) = M:
    % map(P, L, M):
    %
    % Apply the function F or predicate P to transform the elements of L
    % into the elements of M.
    %
:- func map(func(X) = Y, ra_list(X)) = ra_list(Y).
:- pred map(pred(X, Y), ra_list(X), ra_list(Y)).
:- mode map(pred(in, out) is det, in, out) is det.
:- mode map(pred(in, out) is cc_multi, in, out) is cc_multi.
:- mode map(pred(in, out) is semidet, in, out) is semidet.
:- mode map(pred(in, out) is multi, in, out) is multi.
:- mode map(pred(in, out) is nondet, in, out) is nondet.
:- mode map(pred(in, in) is semidet, in, in) is semidet.

    % foldl(Func, List, Start) = End:
    % foldl(Pred, List, Start, End):
    %
    % Calls Func or Pred on each element of List, working left-to-right.
    % Each call to Func or Pred will have a pair of arguments that represent
    % respectively the current and the next value of a piece of state.
    % (Such current-next argument pairs are usually called an accumulator,
    % because the usual use case is that the successive calls to Func or Pred
    % accumulate pieces of information.) The initial value of the accumulator
    % is Start, each call to Func or Pred updates it to the next value, and
    % foldl returns its final value as End.
    %
:- func foldl(func(L, A) = A, ra_list(L), A) = A.
:- pred foldl(pred(L, A, A), ra_list(L), A, A).
:- mode foldl(pred(in, in, out) is det, in, in, out) is det.
:- mode foldl(pred(in, mdi, muo) is det, in, mdi, muo) is det.
:- mode foldl(pred(in, di, uo) is det, in, di, uo) is det.
:- mode foldl(pred(in, in, out) is semidet, in, in, out) is semidet.
:- mode foldl(pred(in, mdi, muo) is semidet, in, mdi, muo) is semidet.
:- mode foldl(pred(in, di, uo) is semidet, in, di, uo) is semidet.
:- mode foldl(pred(in, in, out) is multi, in, in, out) is multi.
:- mode foldl(pred(in, in, out) is nondet, in, in, out) is nondet.
:- mode foldl(pred(in, mdi, muo) is nondet, in, mdi, muo) is nondet.
:- mode foldl(pred(in, in, out) is cc_multi, in, in, out) is cc_multi.
:- mode foldl(pred(in, di, uo) is cc_multi, in, di, uo) is cc_multi.

    % foldr(Func, List, Start) = End:
    % foldr(Pred, List, Start, End):
    %
    % Calls Func or Pred on each element of List, working right-to-left.
    % Each call to Func or Pred will have a pair of arguments that represent
    % respectively the current and the next value of a piece of state.
    % (Such current-next argument pairs are usually called an accumulator,
    % because the usual use case is that the successive calls to Func or Pred
    % accumulate pieces of information.) The initial value of the accumulator
    % is Start, each call to Func or Pred updates it to the next value, and
    % foldl returns its final value as End.
    %
:- func foldr(func(L, A) = A, ra_list(L), A) = A.
:- pred foldr(pred(L, A, A), ra_list(L), A, A).
:- mode foldr(pred(in, in, out) is det, in, in, out) is det.
:- mode foldr(pred(in, mdi, muo) is det, in, mdi, muo) is det.
:- mode foldr(pred(in, di, uo) is det, in, di, uo) is det.
:- mode foldr(pred(in, in, out) is semidet, in, in, out) is semidet.
:- mode foldr(pred(in, mdi, muo) is semidet, in, mdi, muo) is semidet.
:- mode foldr(pred(in, di, uo) is semidet, in, di, uo) is semidet.
:- mode foldr(pred(in, in, out) is multi, in, in, out) is multi.
:- mode foldr(pred(in, in, out) is nondet, in, in, out) is nondet.
:- mode foldr(pred(in, mdi, muo) is nondet, in, mdi, muo) is nondet.
:- mode foldr(pred(in, di, uo) is cc_multi, in, di, uo) is cc_multi.
:- mode foldr(pred(in, in, out) is cc_multi, in, in, out) is cc_multi.

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- implementation.

:- import_module int.
:- import_module require.

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

    % Okasaki's paper represents random access lists as a list of
    % <bintree_size, bintree> pairs. We use a fat-list-style variation
    % of that representation, in which a single three-argument cons cell
    % replaces both a standard two-argument cons cell and a pair cell
    % in Okasaki's representation. This requires fewer memory allocations
    % when constructing ra_lists, and fewer dependent loads when traversing
    % existing ra_lists. (It may also require less memory, though with Boehm,
    % each three argument cons cell requires a four word block.)
    % 
:- type ra_list(T)
    --->    nil
            % The list is empty.
    ;       cons(int, ra_list_bintree(T), ra_list(T)).
            % cons(Size, BinTree, Rest):
            %
            % The list contains the elements of BinTree, followed
            % the elements of Rest. Each BinTree must be a perfect balanced
            % binary tree, which means that all their levels, including their
            % lowest levels, must be completely filled. The Size in each cons
            % gives the number of elements in the BinTree next to it.
            %
            % Due to BinTrees being perfectly balanced, each Size must have
            % the form (2^k) - 1; Okasaki calls such integers "skew-binary
            % terms".
            %
            % With one exception, every ra_list_bintree in the third field
            % of a cons must be strictly bigger than the ra_list_bintree
            % in the second field. The exception is that the first two cons
            % cells in an ra_list may contain bintrees of equal size.

:- type ra_list_bintree(T)
    --->    leaf(T)
            % This tree contains only this item.
    ;       node(T, ra_list_bintree(T), ra_list_bintree(T)).
            % node(I, TL, TR):
            % This tree contains item I, followed by the items in TL,
            % followed by the items in TR. In other words, the order of
            % the items in the tree is given by the *preorder* traversal of
            % the binary tree, not the usual *inorder* traversal.

%---------------------------------------------------------------------------%

:- pragma inline(pred(init/1)).

init(nil).

:- pragma inline(func(singleton/1)).

singleton(X) = cons(1, leaf(X), nil).

:- pragma inline(pred(cons/3)).

cons(X, List0, List) :-
    ( if
        List0 = cons(Size1, T1, cons(Size2, T2, Rest)),
        Size1 = Size2
    then
        NewSize = 1 + Size1 + Size2,
        List = cons(NewSize, node(X, T1, T2), Rest)
    else
        List = cons(1, leaf(X), List0)
    ).

%---------------------------------------------------------------------------%

:- pragma inline(pred(head/2)).

head(cons(_, leaf(X), _), X).
head(cons(_, node(X, _, _), _), X).

:- pragma inline(pred(tail/2)).

tail(cons(_, leaf(_), Tail), Tail).
tail(cons(Size, node(_, T1, T2), Rest), Tail) :-
    Size2 = Size // 2,
    Tail = cons(Size2, T1, cons(Size2, T2, Rest)).

:- pragma inline(pred(head_tail/3)).

head_tail(cons(_, leaf(X), Tail), X, Tail).
head_tail(cons(Size, node(X, T1, T2), Rest), X, Tail) :-
    Size2 = Size // 2,
    Tail = cons(Size2, T1, cons(Size2, T2, Rest)).

%---------------------------------------------------------------------------%

is_empty(RA) :-
    RA = nil.

is_not_empty(RA) :-
    RA = cons(_, _, _).

is_singleton(RA, X) :-
    RA = cons(1, leaf(X), nil).

%---------------------------------------------------------------------------%

length(RAList) = Len :-
    length_acc(RAList, 0, Len).

length(RAList, Len) :-
    length_acc(RAList, 0, Len).

:- pred length_acc(ra_list(T)::in, int::in, int::out) is det.

length_acc(nil, !Len).
length_acc(cons(BinTreeSize, _BinTree, Tail), !Len) :-
    !:Len = !.Len + BinTreeSize,
    length_acc(Tail, !Len).

%---------------------------------------------------------------------------%

:- pragma inline(pred(index0/3)).

index0(List, I0, X) :-
    I0 >= 0,
    index0_list(List, I0, X).

:- pragma inline(pred(index1/3)).

index1(List, I1, X) :-
    I0 = I1 - 1,
    I0 >= 0,
    index0_list(List, I0, X).

:- pred index0_list(ra_list(T)::in, int::in, T::out) is semidet.

index0_list(cons(Size, T, Rest), I, X) :-
    ( if I < Size then
        index0_bintree(Size, T, I, X)
    else
        NewI = I - Size,
        index0_list(Rest, NewI, X)
    ).

:- pred index0_bintree(int::in, ra_list_bintree(T)::in, int::in,
    T::out) is semidet.

index0_bintree(_, leaf(X), 0, X).
index0_bintree(Size, node(X0, T1, T2), I, X) :-
    ( if I = 0 then
        X = X0
    else
        Size2 = Size // 2,
        ( if I =< Size2 then
            NewI = I - 1,
            index0_bintree(Size2, T1, NewI, X)
        else
            NewI = I - 1 - Size2,
            index0_bintree(Size2, T2, NewI, X)
        )
    ).

det_index0(List, I, X) :-
    ( if index0(List, I, XPrime) then
        X = XPrime
    else
        unexpected($pred, "index0 failed")
    ).

det_index1(List, I, X) :-
    ( if index1(List, I, XPrime) then
        X = XPrime
    else
        unexpected($pred, "index1 failed")
    ).

%---------------------------------------------------------------------------%

:- pragma inline(pred(update/4)).

update(I, X, List0, List) :-
    I >= 0,
    update_2(I, X, List0, List).

:- pred update_2(int::in, T::in, ra_list(T)::in, ra_list(T)::out)
    is semidet.

update_2(I, X, List0, List) :-
    List0 = cons(Size, T0, Rest), 
    ( if I < Size then
        update_bintree(Size, I, X, T0, T),
        List = cons(Size, T, Rest)
    else
        NewI = I - Size,
        update_2(NewI, X, Rest, List0),
        List = cons(Size, T0, List0)
    ).

:- pred update_bintree(int::in, int::in, T::in,
    ra_list_bintree(T)::in, ra_list_bintree(T)::out) is semidet.

update_bintree(_, 0, X, leaf(_), leaf(X)).
update_bintree(Size, I, X, node(X0, T1, T2), T) :-
    ( if I = 0 then
        T = node(X, T1, T2)
    else
        Size2 = Size // 2,
        ( if I =< Size2 then
            NewI = I - 1,
            update_bintree(Size2, NewI, X, T1, T0),
            T = node(X0, T0, T2)
        else
            NewI = I - 1 - Size2,
            update_bintree(Size2, NewI, X, T2, T0),
            T = node(X0, T1, T0)
        )
    ).

%---------------------------------------------------------------------------%

append(As, Bs, ABs) :-
    ( if head_tail(As, AH, AT) then
        append(AT, Bs, ATBs),
        cons(AH, ATBs, ABs)
    else
        ABs = Bs
    ).

%---------------------------------------------------------------------------%

drop(N, As, Bs) :-
    ( if N > 0 then
        As = cons(Size, _, Cs),
        ( if Size < N then
            N1 = N - Size,
            drop(N1, Cs, Bs)
        else
            slow_drop(N, As, Bs)
        )
    else
        As = Bs
    ).

:- pred slow_drop(int::in, ra_list(T)::in, ra_list(T)::out) is semidet.

slow_drop(N, As, Bs) :-
    ( if N > 0 then
        N1 = N - 1,
        tail(As, Cs),
        slow_drop(N1, Cs, Bs)
    else
        As = Bs
    ).

%---------------------------------------------------------------------------%

list_to_ra_list(List, RAList) :-
    % We reverse the list and count its elements using tail recursive code.
    reverse_prepend_and_count(List, [], RevList, 0, Size),
    ( if Size = 0 then
        RAList = nil
    else
        % Find the size of the biggest bintree in the RAList we need to
        % construct.
        get_max_bintree_size(Size, 1, MaxSkewBinary),
        % Construct RAList back to front, by repeatedly "chomping off"
        % a bintree's worth of items from RevList.
        acc_ra_list(RevList, Size, MaxSkewBinary, nil, RAList)
    ).

    % Reverse the given list (using non-native reverse) and count
    % the number of items in the list at the same time.
    %
:- pred reverse_prepend_and_count(list(T)::in, list(T)::in, list(T)::out,
    int::in, int::out) is det.

reverse_prepend_and_count([], L, L, !Size).
reverse_prepend_and_count([H | T], L0, L, !Size) :-
    !:Size = !.Size + 1,
    reverse_prepend_and_count(T, [H | L0], L, !Size).

    % Given CurSkewBinary, which is a skew-binary term that is guaranteed
    % not to be bigger than Size, return the largest skew-binary term
    % that is not bigger than Size.
    %
    % This will be the size of the last (and therefore largest) bintree
    % in the ra_list we need to construct for a list with Size items.
    %
:- pred get_max_bintree_size(int::in, int::in, int::out) is det.

get_max_bintree_size(Size, CurSkewBinary, MaxSkewBinary) :-
    NextSkewBinary = (2 * CurSkewBinary) + 1,
    ( if Size >= NextSkewBinary then
        get_max_bintree_size(Size, NextSkewBinary, MaxSkewBinary)
    else
        MaxSkewBinary = CurSkewBinary
    ).

    % acc_ra_list(RevList0, SizeLeftover0, SkewBinary0, RAList0, RAList):
    %
    % We have processed some of the items at the end of the original list
    % (and hence at the start of its reversed form) into RAList0. Convert
    % the remaining elements of the reversed list, RevList0, into one or more
    % cons cells to put in front of RAList0. SizeLeftover0 gives the number
    % of items in RevList0, and SkewBinary0, which must be a skew-binary term,
    % gives the size of the largest bintree in the cons cells we are to put
    % in front of RAList0.
    %
:- pred acc_ra_list(list(T)::in, int::in, int::in,
    ra_list(T)::in, ra_list(T)::out) is det.

acc_ra_list(RevList0, SizeLeftover0, SkewBinary0, RAList0, RAList) :-
    ( if SizeLeftover0 > SkewBinary0 then
        sized_prefix_to_bintree(SkewBinary0, BinTree0, RevList0, RevList1),
        SizeLeftover1 = SizeLeftover0 - SkewBinary0,
        SkewBinary1 = SkewBinary0 // 2,
        RAList1 = cons(SkewBinary0, BinTree0, RAList0),
        ( if SizeLeftover1 < SkewBinary0 then
            % This is the usual case when the cons cells in RevList1 and the
            % next cons cell we put immediately in front of it contain
            % bintrees of different sizes: the sum of all the (2^k) - 1
            % over k in 1..(i-1) is strictly less than (2^i) - 1.
            %
            % For example:
            %
            % for i=2: 1 < 3
            % for i=3: 1 + 3 < 7
            % for i=4: 1 + 3 + 7 < 15
            acc_ra_list(RevList1, SizeLeftover1, SkewBinary1, RAList1, RAList)
        else if SizeLeftover1 = SkewBinary0 then
            % This is how acc_ra_list terminates when the RAList we construct
            % starts with two cons cells that contain bintrees of equal sizes.
            sized_prefix_to_bintree(SkewBinary0, BinTree1, RevList1, RevList),
            RAList = cons(SkewBinary0, BinTree1, RAList0),
            expect(unify(RevList, []), $pred, "RevList != []")
        else
            unexpected($pred, "SizeLeftover0 > SkewBinary0")
        )
    else if SizeLeftover0 < SkewBinary0 then
        SkewBinary1 = SkewBinary0 // 2,
        acc_ra_list(RevList0, SizeLeftover0, SkewBinary1, RAList0, RAList)
    else
        % This is how acc_ra_list terminates when the RAList we construct
        % starts with two cons cells that contain bintrees of non-equal sizes.
        sized_prefix_to_bintree(SkewBinary0, BinTree, RevList0, RevList1),
        SizeLeftover1 = SizeLeftover0 - SkewBinary0,
        RAList = cons(SkewBinary0, BinTree, RAList0),
        expect(unify(RevList1, []), $pred, "RevList1 != []"),
        expect(unify(SizeLeftover1, 0), $pred, "SizeLeftover1 != 0")
    ).

    % sized_prefix_to_bintree(BinTreeSize, BinTree, !RevList) :-
    %
    % Take the first BinTreeSize elements of !RevList, and construct
    % a perfectly balanced binary tree out of them, while undoing the reversal.
    %
    % BinTreeSize must be a skew-binary term.
    %
:- pred sized_prefix_to_bintree(int::in, ra_list_bintree(T)::out,
    list(T)::in, list(T)::out) is det.

sized_prefix_to_bintree(BinTreeSize, BinTree, !RevList) :-
    ( if BinTreeSize = 1 then
        get_next_item_from_rev_list(Head, !RevList),
        BinTree = leaf(Head)
    else
        % Given BinTreeSize = (2^k) - 1, this sets BinTreeSize2
        % to (2^(k-1)) - 1, i.e. to the next lower skew-binary term.
        BinTreeSize2 = BinTreeSize // 2,
        sized_prefix_to_bintree(BinTreeSize2, R, !RevList),
        sized_prefix_to_bintree(BinTreeSize2, L, !RevList),
        get_next_item_from_rev_list(Head, !RevList),
        BinTree = node(Head, L, R)
    ).

:- pred get_next_item_from_rev_list(T::out, list(T)::in, list(T)::out) is det.

get_next_item_from_rev_list(Head, !RevList) :-
    (
        !.RevList = [Head | !:RevList]
    ;
        !.RevList = [],
        unexpected($pred, "!.RevList = []")
    ).

%---------------------------------------------------------------------------%

ra_list_to_list(RAList, List) :-
    (
        RAList = nil,
        List = []
    ;
        RAList = cons(_Size, BinTree, RAListTail),
        ra_list_to_list(RAListTail, Tail),
        ra_list_to_list_acc_bintree(BinTree, Tail, List)
    ).

:- pred ra_list_to_list_acc_bintree(ra_list_bintree(T)::in,
    list(T)::in, list(T)::out) is det.

ra_list_to_list_acc_bintree(BinTree, Tail0, List) :-
    (
        BinTree = leaf(X),
        List = [X | Tail0]
    ;
        BinTree = node(X, L, R),
        ra_list_to_list_acc_bintree(R, Tail0, Tail1),
        ra_list_to_list_acc_bintree(L, Tail1, Tail2),
        List = [X | Tail2]
    ).

%---------------------------------------------------------------------------%

map(_F, nil) = nil.
map(F, cons(Size, BinTree0, RAList0)) = cons(Size, BinTree, RAList) :-
    BinTree = map_bintree(F, BinTree0),
    RAList = map(F, RAList0).

map(_P, nil, nil).
map(P, cons(Size, BinTree0, RAList0), cons(Size, BinTree, RAList)) :-
    map_bintree(P, BinTree0, BinTree),
    map(P, RAList0, RAList).

:- func map_bintree(func(X) = Y, ra_list_bintree(X)) = ra_list_bintree(Y).
:- pred map_bintree(pred(X, Y), ra_list_bintree(X), ra_list_bintree(Y)).
:- mode map_bintree(pred(in, out) is det, in, out) is det.
:- mode map_bintree(pred(in, out) is cc_multi, in, out) is cc_multi.
:- mode map_bintree(pred(in, out) is semidet, in, out) is semidet.
:- mode map_bintree(pred(in, out) is multi, in, out) is multi.
:- mode map_bintree(pred(in, out) is nondet, in, out) is nondet.
:- mode map_bintree(pred(in, in) is semidet, in, in) is semidet.

map_bintree(F, BinTree0) = BinTree :-
    (
        BinTree0 = leaf(X0),
        X = F(X0),
        BinTree = leaf(X)
    ;
        BinTree0 = node(X0, L0, R0),
        X = F(X0),
        L = map_bintree(F, L0),
        R = map_bintree(F, R0),
        BinTree = node(X, L, R)
    ).

map_bintree(P, BinTree0, BinTree) :-
    (
        BinTree0 = leaf(X0),
        P(X0, X),
        BinTree = leaf(X)
    ;
        BinTree0 = node(X0, L0, R0),
        P(X0, X),
        map_bintree(P, L0, L),
        map_bintree(P, R0, R),
        BinTree = node(X, L, R)
    ).

%---------------------------------------------------------------------------%

foldl(_F, nil, !.A) = !.A.
foldl(F, cons(_Size, BinTree, RAList), !.A) = !:A :-
    !:A = foldl_bintree(F, BinTree, !.A),
    !:A = foldl(F, RAList, !.A).

foldl(_P, nil, !A).
foldl(P, cons(_Size, BinTree, RAList), !A) :-
    foldl_bintree(P, BinTree, !A),
    foldl(P, RAList, !A).

:- func foldl_bintree(func(L, A) = A, ra_list_bintree(L), A) = A.
:- pred foldl_bintree(pred(L, A, A), ra_list_bintree(L), A, A).
:- mode foldl_bintree(pred(in, in, out) is det, in, in, out) is det.
:- mode foldl_bintree(pred(in, mdi, muo) is det, in, mdi, muo) is det.
:- mode foldl_bintree(pred(in, di, uo) is det, in, di, uo) is det.
:- mode foldl_bintree(pred(in, in, out) is semidet, in, in, out) is semidet.
:- mode foldl_bintree(pred(in, mdi, muo) is semidet, in, mdi, muo) is semidet.
:- mode foldl_bintree(pred(in, di, uo) is semidet, in, di, uo) is semidet.
:- mode foldl_bintree(pred(in, in, out) is multi, in, in, out) is multi.
:- mode foldl_bintree(pred(in, in, out) is nondet, in, in, out) is nondet.
:- mode foldl_bintree(pred(in, mdi, muo) is nondet, in, mdi, muo) is nondet.
:- mode foldl_bintree(pred(in, in, out) is cc_multi, in, in, out) is cc_multi.
:- mode foldl_bintree(pred(in, di, uo) is cc_multi, in, di, uo) is cc_multi.

foldl_bintree(F, BinTree, !.A) = !:A :-
    (
        BinTree = leaf(X),
        !:A = F(X, !.A)
    ;
        BinTree = node(X, L, R),
        !:A = F(X, !.A),
        !:A = foldl_bintree(F, L, !.A),
        !:A = foldl_bintree(F, R, !.A)
    ).

foldl_bintree(P, BinTree, !A) :-
    (
        BinTree = leaf(X),
        P(X, !A)
    ;
        BinTree = node(X, L, R),
        P(X, !A),
        foldl_bintree(P, L, !A),
        foldl_bintree(P, R, !A)
    ).

%---------------------------------------------------------------------------%

foldr(_F, nil, !.A) = !.A.
foldr(F, cons(_Size, BinTree, RAList), !.A) = !:A :-
    !:A = foldr(F, RAList, !.A),
    !:A = foldr_bintree(F, BinTree, !.A).

foldr(_P, nil, !A).
foldr(P, cons(_Size, BinTree, RAList), !A) :-
    foldr(P, RAList, !A),
    foldr_bintree(P, BinTree, !A).

:- func foldr_bintree(func(L, A) = A, ra_list_bintree(L), A) = A.
:- pred foldr_bintree(pred(L, A, A), ra_list_bintree(L), A, A).
:- mode foldr_bintree(pred(in, in, out) is det, in, in, out) is det.
:- mode foldr_bintree(pred(in, mdi, muo) is det, in, mdi, muo) is det.
:- mode foldr_bintree(pred(in, di, uo) is det, in, di, uo) is det.
:- mode foldr_bintree(pred(in, in, out) is semidet, in, in, out) is semidet.
:- mode foldr_bintree(pred(in, mdi, muo) is semidet, in, mdi, muo) is semidet.
:- mode foldr_bintree(pred(in, di, uo) is semidet, in, di, uo) is semidet.
:- mode foldr_bintree(pred(in, in, out) is multi, in, in, out) is multi.
:- mode foldr_bintree(pred(in, in, out) is nondet, in, in, out) is nondet.
:- mode foldr_bintree(pred(in, mdi, muo) is nondet, in, mdi, muo) is nondet.
:- mode foldr_bintree(pred(in, in, out) is cc_multi, in, in, out) is cc_multi.
:- mode foldr_bintree(pred(in, di, uo) is cc_multi, in, di, uo) is cc_multi.

foldr_bintree(F, BinTree, !.A) = !:A :-
    (
        BinTree = leaf(X),
        !:A = F(X, !.A)
    ;
        BinTree = node(X, L, R),
        !:A = foldr_bintree(F, R, !.A),
        !:A = foldr_bintree(F, L, !.A),
        !:A = F(X, !.A)
    ).

foldr_bintree(P, BinTree, !A) :-
    (
        BinTree = leaf(X),
        P(X, !A)
    ;
        BinTree = node(X, L, R),
        foldr_bintree(P, R, !A),
        foldr_bintree(P, L, !A),
        P(X, !A)
    ).

%---------------------------------------------------------------------------%
:- end_module ra_list.
%---------------------------------------------------------------------------%
