Search code examples
prologpalindromedifference-lists

Understanding difference lists (Prolog)


I'm having trouble understanding difference list, particularly in this predicate:

palindrome(A, A).
palindrome([_|A], A).
palindrome([C|A], D) :-
   palindrome(A, B),
   B=[C|D].

Could anyone help me follow what's happening?


Solution

  • palindrome(A, A).
    palindrome([_|A], A).
    palindrome([C|A], D) :-
       palindrome(A, B),
       B=[C|D].
    

    Seeing the arguments to this predicate as a difference list, the first clause says, a list from A to A (i.e., an empty list) is a palindrome.

    The second clause says, a one-element list is a palindrome, whatever that one element is.


    Don't panic!  Difference lists are just lists with explicit end "pointer"

    A normal list, say [1,2,3], is a difference between its start and its end; the end of a normal list is always an empty list, []. That is to say, for a list [1,2,3] we are supposed to call this predicate as palindrome( [1,2,3], []) — namely, check whether the difference list [1,2,3] - [] is a palindrome.

    From the operational point of view, a difference list is nothing but a (possibly open-ended) list with explicitly maintained "end pointer", for example: A - Z where A = [1,2,3|Z] and Z = []. Indeed, [1,2,3|[]] is the same as [1,2,3]. But when Z is not instantiated yet, the list A is still open ended - its "end pointer" Z can be instantiated to anything (but only once, of course, sans the backtracking).

    If we were to instantiate Z later to an open-ended list, say, Z = [4|W], we'd get a new, extended difference list A - W where A = [1,2,3,4|W]. The old one would become A - Z = [1,2,3,4|W] - [4|W], i.e. still representing a prefix [1,2,3] of an open-ended list [1,2,3,4 ...]. Once closed, e.g. with W = [5], all the pairs of logvars still represent their corresponding difference lists (i.e. A - Z, A - W ...), but A is not open-ended anymore, so can't be extended anymore.

    Instead of using the - functor, it is customary to just use both parts of the diff list definition as separate arguments to a predicate. When we always use / treat them as if they were two parts of a pair, then they form a pair, conceptually. It's the same thing.


    Continuing. The third clause says, for [C|A]-D to be a palindrome, A-B must be a palindrome, and B must be [C|D]. A, D, B are lists, C is an element of a list. This might be confusing; let's use V instead. Also, use Z and Y instead of D and B, to remind us of "the end" of a list:

    palindrome([V|A], Z):- palindrome(A, Y), Y=[V|Z].
    
    V ................. V ----
      ^                 ^ ^
      |                 | |
      |                 | Z
      A                 Y = [V|Z]
    

    Indeed, when the ...... core is a palindrome, putting two Vs around it gives us another palindrome.