Search code examples
prologfailure-slice

Why does my list reversal only work correctly in one direction?


I have produced the following code.

list_reverse([],[]). 
list_reverse([X],[X]).
list_reverse(Ls,[R|Rs]) :-
    last_elem(Ls,R),
    without_last_elem(Ls,Next),
    list_reverse(Next,Rs).

last_elem([E],E).
last_elem([_|Xs],E) :-
    last_elem(Xs,E).

without_last_elem([X,_|[]],[X|[]]).
without_last_elem([X|T0],[X|T1]) :-
    without_last_elem(T0,T1).

Swipl:

?- list_reverse([1,2,3],X).
X = [3, 2, 1] ;
false.

This is exactly what I want.

However if I go in the opposite direction I get success, followed by non-termination.

?- list_reverse(X,[1,2,3]).
X = [3, 2, 1] ;
  C-c C-cAction (h for help) ? a
abort
% Execution Aborted

What I am struggling to understand is why I first get a correct solution for X. Is my program correct or not?

I am not worried about reversing a list as much as I am about this pattern of getting a correct solution followed by non-termination. It is a pattern I have already come across a few times.


Solution

  • The last_elem/2 can keep constructing larger lists, that all should be rejected. But you thus get stuck in an infinite loop.

    We can make a function that works with accumulator, and iterates over both the two lists concurrently. That means that once the left or right list is exhausted, no more recursive calls will be made:

    reverse(L1, L2) :-
        reverse(L1, [], L2, L2).
    
    reverse([], L, L, []).
    reverse([H|T], L1, R, [_|T2]) :-
        reverse(T, [H|L1], R, T2).

    Here the [H|T] and [_|T2] pattern thus both pop the first item of the list, and we only match if both lists are exhausted.