Prolog Accumulators. Are they really a “different” concept?

When you give something a name, it suddenly becomes more real than it used to be. Discussing something can now be done by simply using the name of the concept. Without getting any more philosophical, no, there is nothing special about accumulators, but they are useful.

In practice, going through a list without an accumulator:

foo([]).
foo([H|T]) :-
    foo(T).

The head of the list is left behind, and cannot be accessed by the recursive call. At each level of recursion you only see what is left of the list.

Using an accumulator:

bar([], _Acc).
bar([H|T], Acc) :-
    bar(T, [H|Acc]).

At every recursive step, you have the remaining list and all the elements you have gone through. In your len/3 example, you only keep the count, not the actual elements, as this is all you need.

Some predicates (like len/3) can be made tail-recursive with accumulators: you don’t need to wait for the end of your input (exhaust all elements of the list) to do the actual work, instead doing it incrementally as you get the input. Prolog doesn’t have to leave values on the stack and can do tail-call optimization for you.

Search algorithms that need to know the “path so far” (or any algorithm that needs to have a state) use a more general form of the same technique, by providing an “intermediate result” to the recursive call. A run-length encoder, for example, could be defined as:

rle([], []).
rle([First|Rest],Encoded):- 
    rle_1(Rest, First, 1, Encoded).               

rle_1([], Last, N, [Last-N]).
rle_1([H|T], Prev, N, Encoded) :-
    (   dif(H, Prev) 
    ->  Encoded = [Prev-N|Rest],
        rle_1(T, H, 1, Rest)
    ;   succ(N, N1),
        rle_1(T, H, N1, Encoded)
    ).

Hope that helps.

Leave a Comment