CS 4536 BS/MS Assignment: Implementing Prolog

Due: Ideally by end of term, but negotiable. Submit by email to Kathi
Collaboration Policy: Pairs Permitted

The Assignment

For this assignment, you will implement the core of Prolog as outlined below. This requires a combination of continuations and a bit of macros (as you saw in 2135/1102).

For an introduction to Prolog and its implementation, read chapters 33 and 34 of the text. If you are new to Prolog, you should run at least the programs from chapter 33 to get a feel for it. SWI Prolog is available on CCC Unix (command "pl" starts the evaluator), or you can download it to your own computer.

This is a challenging assignment, pulling together various concepts from across the course. If you've struggled with the assignments so far, you are unlikely to be able to do this one.

Grading will be done by an oral code review (aka "codewalk"). During the codewalk, you'll be asked to present your implementation and to explain how it solves the problem. For pair submissions, both students are expected to participate in codewalk. This will confirm that both of you understand the submitted solution. We will schedule codewalks after you are done with the assignment.

As this assignment has no bearing on your undergrad course grade, I can be flexible with the due date. The main constraint is that I will be out of the country for a month starting a few days after C term ends. Ideally, I'd like to run the codewalks by the Tuesday after C term. If you want to work on this through break though, we can schedule your codewalk for mid-April instead.


What to Implement

In this assignment, you will implement Prolog-style logic variables and backtracking search using Scheme's macros and continuations. In particular, you must implement the following:

Some of these abstractions may be implementable as Scheme procedures, while others will require the use of macros. In either case, recall the pattern described in the text for performing backtracking: the unification and search primitives (i.e., ==, =succeed, =fail, =or, and =and) consume a failure continuation (to be invoked on failure) and, if successful, return a success continuation (to allow resumption of the search). This pattern prevents the Prolog embedding from communicating ordinary program answers through procedure return values. Instead it uses logic variables, which you will need to update imperatively, taking care to restore when backtracking. In addition to these linguistic extensions, you must also implement the following testing interface: For interactive testing, you may include the following definitions for next (produces next solution) and show (starts producing solutions):
(define resumer-box (box 'resumer))

(define (next) ((unbox resumer-box) 'dummy))

(define-syntax show
  (syntax-rules ()
    [(show (vars ...) e)
     (=var (vars ...)
           (let/cc esc
             (let/cc fk
               (esc (let ([next (e fk)])
                      (set-box! resumer-box next)
                      (printf "~a: ~a~n" (quote vars) (lv-value vars))
                      ...
                      (void))))
             'fail))]))

Example 1: Simple Search

You might want to start by just testing =and and =or with success and failure (no logic variables). In this restricted setting, there are a couple of useful properties: namely, if e1 succeeds in n1 ways, e2 succeeds in n2 ways, etc., then (=or e1 e2 ...) succeeds in n1 + n2 + ... ways, and (=and e1 e2 ...) succeeds in n1 * n2 * ... ways. For instance,

(=and (=or =succeed =succeed =fail =succeed)
      (=or =fail =succeed =succeed))
succeeds in six ways. The first =or succeeds in three ways, the second =or succeeds in two ways, and the =and combines them in all possible ways. Likewise,
(=or (=and =succeed =succeed)
     (=and =fail =succeed =succeed)
     (=and =succeed))
succeeds in two ways. The first and third =ands each succeed in one way, and the =or explores both of them.

You can use show (with an empty variable list) and next to count how many times a query succeeds.

Example 2: Family Trees

As a more complicated example, suppose we have the following definitions:

(define (=parent c p)
  (=or (=and (== c 'vito) (== p 'dom))
       (=and (== c 'sonny) (== p 'vito))
       (=and (== c 'michael) (== p 'vito))
       (=and (== c 'fredo) (== p 'vito))
       (=and (== c 'sophia) (== p 'michael))
       (=and (== c 'tony) (== p 'michael))))

(define (=ancestor X Y)
  (=or (=parent X Y)
       (=var (Z)
             (=and (=parent X Z)
                   (=ancestor Z Y)))))
Then we get the following query results:
> (show (x) (=ancestor x 'vito))
x: sonny
> (next)
x: michael
> (next)
x: fredo
> (next)
x: sophia
> (next)
x: tony
> (next)
'fail
> (find-all (x) (=parent x 'michael))
(list (list (list 'x 'sophia))
      (list (list 'x 'tony)))
> (find-some 3 (x y) (=ancestor x y))
(list (list (list 'x 'vito) (list 'y 'dom))
      (list (list 'x 'sonny) (list 'y 'vito))
      (list (list 'x 'michael) (list 'y 'vito)))


What to Turn In

Submit a single Scheme program containing your implementation. Make sure all participating student's names are at the top of the file (in a comment).


FAQ

Nothing yet ...