I. The Swiss Army Knife of CILOG predicates Even though I put the text about append in the notes for the previous lecture in case it proved to helpful in doing your homework, we didn't really talk about it class until today. So below are those notes repeated. In addition, we talked about how some of the homework problems could have been solved with append, and those solutions show up in the posted solutions for assignment 2. The purpose of append is to join two lists together. More accurately, the purpose is to prove that one particular list is the result of joining two other specific lists together. For example, we might want to prove: append([a,b],[c,d],[a,b,c,d]). To find out the result of appending [a,b] and [c,d], we'd use the more general form of the theorem: append([a,b],[c,d],X). How do we look at this problem? Don't be discouraged if the answer isn't obvious. Defining append always seems to require some breakthrough insight. The insight here is that append([a,b],[c,d],[a,b,c,d]) is true if append([b],[c,d],[b,c,d]) is true (and a=a, which is true of course). This is true if append([],[c,d],[c,d]) is true (and b=b, which is also true). Is there any need to go further? Nah. So the generalized base case looks like this: append([],X,X). Those reductions and tests of equality can be encoded like this: append([Head|Rest1], List2, [Head|Rest3]) <- append(Rest1, List2, Rest3). The utility of append comes from its ability to split lists by partially specifying the nature of the split lists. For example, you can find the possible front parts of a list by specifying just the back part of the list: cilog: ask append(X,[c,d],Y). Answer: append([], [c, d], [c, d]). Runtime since last report: 0 secs. [ok,more,how,help]: more. Answer: append([A], [c, d], [A, c, d]). Runtime since last report: 0 secs. [ok,more,how,help]: more. Answer: append([A, B], [c, d], [A, B, c, d]). Runtime since last report: 0 secs. [ok,more,how,help]: more. Answer: append([A, B, C], [c, d], [A, B, C, c, d]). Runtime since last report: 0 secs. [ok,more,how,help]: Or you can find all the different ways to split one list into two: cilog: ask append(X,Y,[a,b,c]). Answer: append([], [a, b, c], [a, b, c]). Runtime since last report: 0 secs. [ok,more,how,help]: more. Answer: append([a], [b, c], [a, b, c]). Runtime since last report: 0 secs. [ok,more,how,help]: more. Answer: append([a, b], [c], [a, b, c]). Runtime since last report: 0 secs. [ok,more,how,help]: more. Answer: append([a, b, c], [], [a, b, c]). Runtime since last report: 0 secs. [ok,more,how,help]: more. No more answers. Runtime since last report: 0 secs. cilog: Furthermore, lots of other problems can be viewed as instances of list splitting, and the proof procedures associated with them can be defined in terms of append: prefix(X,Y) <- append(X,M,Y) Essentially, this says that X is a prefix of Y if there's some mystery list M that can be appended to the back of X to produce Y. The suffix procedure can be redefined in terms of append too: suffix(X,Y) <- append(M,X,Y) In other words, X is a suffix of Y if there's some list M that can be appended to the front of X to produce Y. The member predicate can be viewed as list splitting: member(X,Y) <- append(M,[X|Rest],Y) Which can be read as X is a member of Y if there's some list M that can be appended to the front of another list whose first element is X, and the result of the appending is the list Y.
Last revised: October 3, 2004