Homework 9 - Fun with Prolog

Due: Nov 23 11:59 PM

Note that we don't have class on the 22nd due to Thanksgiving. The Prolog project (HW 10) will be posted Friday (hopefully earlier), so that you can work off turkey leftovers during break.

Background

The previous homework introduced you to Prolog, and in particular the purely declarative parts of Prolog.  Unfortunately, while the logic programming paradigm is declarative, the Prolog implementation of logic programming needs various non-declarative constructs.  Most (but not all) of these constructs are for efficiency reasons, while some are there 'just because'.  The purpose of this assignment is to get you more familiar with non-declarative parts of Prolog, and have fun doing it.  You will probably need to use the debugger for this (see notes on using the debugger).

As always, you need to document predicate modes for every predicate you write, and test your code adequately. You are still NOT allowed to use library predicates other than the ones you could use in the previous homework. However, unlike the previous assignment, you are allowed to use extralogical constructs such as cut and not (of course, only in appropriate contexts). The general lesson to draw for Prolog programming is that you should think purely declaratively, but can use non-declarative constructs when absolutely needed AFTER spending some time thinking about whether you really need them. 

Problem 1

In class we started the wolf-goat-cabbage problem as follows:
 
% The state is a list [F,W,G,C], representing the banks of the farmer, wolf, goat,
% and cabbage, respectively.
unsafe([B1,B2,B2,_]) :- otherbank(B1,B2).
...
moveone([B,B,Bg,Bc],[B2,B2,Bg,Bc]) :- otherbank(B,B2).
...
otherbank(e,w).  otherbank(w,e).
solve :- move([e,e,e,e],[w,w,w,w]).
move(State1,State2) :-
  moveone(State1,State2), !.
move(State1,State2) :-
  moveone(State1,State3),
  not(unsafe(State3)),
  move(State3,State2).
As we mentioned in class, there are 2 problems with this code: 1) it cycles between states [e,e,e,e] and [w,w,e,e], and 2) it only gives a yes or no answer while we want a path to be returned.

Modify the above code to eliminate these 2 problems.  DO NOT write some other implementation of the puzzle.

Problem 2

Consider the function f(n)=n if n<6  or f(n-1)*f(n-3)+2*f(n-3) if n>5.  Such functions are notoriously inefficient in non-imperative languages since they recompute smaller cases many times.   For this problem, you will explore two common ways to address this problem in the functional and logic paradigms, and evaluate the resulting performances.  First write 3 programs to implement the predicate f(+N,-Result), calling them f1, f2 and f3:
  1. The direct recursive implementation
  2. Use accumulator variables to pass as many 'previous' values as needed.
  3. Dynamically insert f(N,F) into the database as soon as you compute it, using one of prolog's assert/1 predicates (you should look through the documentation to pick the correct assert predicate).  Note that this essentially prevents you from ever reducing the same predicate/arguments twice. This is called memoization (also, tabling).  Some compilers for logic programming languages do it automatically; however, most imperative languages are too unclean for compilers to do safe memoization.
After getting your programs to work, compare their performances using prolog's time/1 predicate.  Write a brief 1-paragraph summary (in clearly labeled comments) comparing the performances of the direct, accumulator, and memoization approaches. 

Also explain whether or not a compiler in a language without referential transparency can implement memoization?  A 1 or 2 sentence explanation suffices.

Extra note: The use of assert and retract are considered VERY bad prolog style and to be avoided at all costs since they are essentially state variables.  In this course (and qualifiers), their use is absolutely illegal except where otherwise stated.  They are also often highly inefficient, since Prolog compiler optimization techniques often can't be applied  in the presence of these.

Problem 3

Create a small database with father/2 and mother/2 facts (make the first argument the parent and the 2nd argument the child).  There are no stepparents/etc. in this world. Make sure your database is sufficient to test the predicates you will write below. Write the following predicates (making non-specified modes as general as you can):
  1. onlyChild(+Person) succeeds if Person is an only child.
  2. siblings(+Person,Siblings) succeeds if Siblings is the set of Person's siblings. 
  3. cousinNthRemoved(+Person,N,Rem,Cousin) succeeds if Cousin is Person's Nth cousin Rem-removed. 
  4. allGenCousins(+Person,Gen,Cousins) succeeds if Cousins is the set of all cousins of Person in generation Gen (including 1st, 2nd, 3rd,... cousins). We define Person's generation to be generation 0.
You may wish to check here for useful definitions.

You may use Prolog's built-in higher-order predicates (bagof, setof, findall) for this problem. See SWI's help to learn about these. Note that you can achieve the equivalent effect using accumulators, though that makes the code ugly.

Submission

The programs should be submitted electronically to the grader and cc'd to me.  All programs should be in one file, named  <firstname>_<surname>_hw9.pl. 

Hints/Clarifications/Corrections

  1. You can get help on any built-in predicate by typing "help(<predicate>)" from the swi prompt.
  2. My implementation of the wolf-goat-cabbage puzzle ran essentially instantaneously, and it really shouldnt take much longer since there are only 16 possible states. SWI Prolog includes a profiler you can use if yours takes too long - type "profile(solve).".
  3. You may need to use dynamic/1 and multifile/1 when dynamically changing the database.  Use SWI's help (be careful to follow exactly what it says syntactically).
  4. If you are working with large structures, Prolog may just display part of it, with a "..." replacing other parts.  Prolog needs to do this since structures may be infinite (and to avoid flooding your screen even for finite structures).  If you want to see the entire structure, you can do one of two things:
    1. use print/1 or write/1 either in the query (e.g. query(...),print(X)) or in the program itself.
    2. Modify prolog's default print depth.  To view the current print depth, type  prolog_flag(toplevel_print_options,X).  To change the print depth, type set_prolog_flag(toplevel_print_options,<modified X>), where you have modified X to the appropriate print depth.