Search code examples
prologterminationfailure-slice

Termination condition prolog


My teacher provided us with some slides regarding Prolog and I found something a bit weird.

reverse([],[]).
reverse([X|Xs],Zs) :- reverse(Xs,Ys), append(Ys, [X], Zs).

According to him, the program terminates when the 1st argument reverse([],..) is a complete list. Furthermore, if you switch the objectives within the predicate to reverse([X|Xs],Zs) :- append(Ys, [X], Zs), reverse(Xs,Ys). the program should terminate when the 2nd argument is a complete list reverse(..,[]).

This goes a bit against what i've learnt so far. I thought that both of the arguments influenced the program's termination condition and apparently they do not according to my teacher's example. Can anyone give me some input on this?


Solution

  • The termination property of Prolog programs is a bit difficult to grasp due to Prolog's relatively complex control flow. There are ways to cut down the complexity. One, is to consider only parts of your program by inserting additional goals false into your program. You can place them in any place. And no matter where you place them the following holds: If this new program, called a failure-slice does not terminate, then also your original program does not terminate. Note the "if". Here is your program:

    reverse([],[]) :- false.
    reverse([X|Xs],Zs) :-
       reverse(Xs,Ys), false,
       append(Ys, [X], Zs).
    

    This failure-slice is completely useless for understanding what your relation describes. It will never succeed. However, it helps us to better understand how termination will do.

    Note that the fact is completely eliminated. In fact, no matter what the fact looks like it cannot improve termination of this failure-slice of reverse/2. (It could deteriorate termination, though).

    Also note the second argument, the Zs: there is no further mentioning of this Zs. Thus: the second argument of reverse/2 can be whatever it wants to be. It will again not improve termination.

    To make reverse/2 terminate, the first argument has to be instantiated such that this fragment will terminate. Thus [], [X|[]] will terminate, even [X|nonlist] will terminate. But partial lists such as Xs, [a|Xs] etc will not terminate.

    If you want improve termination for, say, reverse(Xs,[]) you need to change something in the visible, remaining part. One way is to exchange the goals. Now, Zs may contribute to the termination. But -alas- the first argument has now no longer the influence it used to have. Consider reverse([a], Zs) and:

    reverse([],[]) :- false.
    reverse([X|Xs],Zs) :-
       append(Ys, [X], Zs), false,
       reverse(Xs,Ys).
    
    append([], Zs, Zs) :- false.
    append([X|Xs], Ys, [X|Zs]) :-
       append(Xs, Ys, Zs), false.
    

    So while this fragment still insists that the first argument is [_|_] it does not take the remaining term into account. And thus, the goal will not terminate.

    If you want to learn more, look at . Equally, consider using cTI. To develop a good intuition, you will need to try it out yourself.