@comment(Hey, EMACS, this is -*- SCRIBE -*- input)
@make(6001)
@set(chapter=1)
@set(page=1)

@PageHeading(even,
             left "@Value(Page)",
             right "6.001 -- Fall Semester 1984")

@PageHeading(odd,
             Left "Problem Set 8",
             right "@value(page)")

@begin(center)
MASSACHUSETTS INSTITUTE OF TECHNOLOGY
Department of Electrical Engineering and Computer Science
6.001 Structure and Interpretation of Computer Programs

Problem Set 8
@end(center)
@blankspace(0.25 in)

Issued: 6 November, 1984

Due: Wednesday, 21 November, 1984, for ALL sections.

Reading Assignment: Sections 4.1 through 4.4 (Note that this is a
2-week assignment)


@b[Exam notice:] Exam #2 will be held on Thursday, November 15, 1984.
The exam will be held in Walker Memorial Gymnasium (50-340) from 5-7PM
xor 7-9PM.  You may take the exam during either one of these two
periods, but students taking the exam during the first period will not
be allowed to leave the room until the end of the period.  The exam is
open book.  It will cover material from the beginning of the semester
through the material in Problem Set 7, and in the text through the
end of chapter 3.
 

@begin(center)
@b[Exercises]
@end(center)

Write up and turn in the following exercises from chapter 4 of
the text:

@begin(itemize)
Exercise 4.1 -- page 299

Exercise 4.16 -- page 323

Exercise 4.17 -- page  324
@end(itemize)

@newpage()

@begin(center)
@b[Delayed Evaluation]
@end(center)

This problem set asks you to modify the Lisp evaluator to allow for
procedures that pass parameters in a ``call-by-name'' style.  The
necessary modification is outlined in section 4.2.1 of the
text.  Although this modification does not require writing a great
deal of code, you will need to have a good understanding of the
evaluator in order to do this problem set.  Read the beginning of
chapter 4 and plan your work carefully before coming to the lab.  

@paragraph(Pre-lab assignment)

The following problem should be done before beginning work on the lab
assignment: 

The interpreter given in Section 4.1, unlike the full Scheme
interpreter, does not have an @a[if] construct, only a @a[cond].
Louis Reasoner claims that in fact @a[if] is unnecessary, since we
could define it as an ordinary Lisp procedure:

@begin(example)
(define (if predicate action alternative)
  (cond ((predicate action)
         (else alternative))))
@end(example)

Explain why this does not work.  In particular, suppose you use this @a[if]
procedure to define @a[factorial]:

@begin(example)
(define (factorial n)
  (if (= n 0)
      1
      (* n (factorial (- n 1)))))
@end(example)

What happens when you attempt to evaluate @a[(factorial 3)] ?  Why. 

We realize that you have seen this problem before, but we want you to
be thinking about this issue before going on to the rest of the
assignment.  Note also that you cannot try out the above definitions
in an ordinary Scheme, since Scheme's own definition of @a[if] will
interfere with the one you attempt to define.  (Ordinarily, there is
nothing to stop you from re-defining Scheme primitives, but @a[if] is
implemented in Scheme as a special form.)

@paragraph(In the laboratory)

The code file for this problem set contains a copy of a Lisp
interpreter in Lisp, essentially identical to the one in Section 4.1.
There are a few minor differences, of which the most important are:

@begin(itemize)
The procedures @a[eval] and @a[apply] have been renamed @a[mini-eval]
and @a[mini-apply], so as not to interfere with Scheme's own @a[eval]
and @a[apply] operators.  (Footnote 12 on page 315 indicates
how we could have gotten around this.  But -- with two evaualtors
floating around -- there is already enough potential for confusion in
doing this problem set.)

The interface to the underlying Scheme system via
@a[apply-primitive-procedure] is handled somewhat differently from in
the text.  This provides an easy way to ``snarf'' primitives from the
underlying Scheme.

The driver loop uses a procedure called @a[user-print] instead of
@a[print]. 
@a[User-print] takes care not to try to print
the environment part of a procedure, since that is often a circular
structure.
@end(itemize)
See the attached code for more details.

If you load this file into Scheme and type @a[initialize-lisp], you
will find yourself typing at the driver loop.  Please note that this
Lisp running in Lisp contains no error system.  If you hit an error or
type ctrl-G, you will bounce back into Scheme.  Also, you must type in
function definitions directly to the driver loop, since there is no
interface to the editor.  In order to help you keep from becoming
confused about which ``Lisp'' you are typing at, the driver loop uses
the prompt @a[**==>] instead of the ordinary scheme prompt @a[-->].

Start up the interpreter and try a few simple expressions.  If you
bounce out into Scheme, you can re-enter the interpreter by typing
@a[(driver-loop)].  If you get hopelessly fouled up, you can run
@a[initialize-lisp], but this initializes the environment, and you
will lose any definitions you have made.

Also, it is instructive to run the interpreter while tracing
@a[mini-eval] and/or @a[mini-apply], to see just how the evaluator
works.  (You will also probably need to do this while debugging your
code for this assignment.)  If you do trace these functions, be sure
set Scheme's @a[print-depth] and @a[print-breadth] to reasonably small
values, e.g., 5, because the @a[env] argument to these procedures
(printed by the tracer) is in general a circular list.  (See the
section of the ``Don't Panic'' manual on print level and print
breadth.)

For this assignment, you will need to modify a few of the procedures in the
interpreter, and also write a few new procedures.  These have been
placed separately in a modifications file, to save you the need to
work with the entire evalautor code in the editor buffer.


@paragraph(Lab problem 1)

Section 4.2.1 of the text sketches the modification you are to make
to the evaluator.  Filling in the details will require modifying a few
of the procedures already given, and also writing new procedures (for
example, to handle thunks).  Be sure to test your implementation.  For
all of these lab problems, turn in listings of the procedures that you
write and modify.

As part of your testing process, try the following example from p. 316
of the text:

@begin(example)
**==>(define (try (delayed a) (delayed b))
        (cond ((= a 0) 1)
              (else b)))

**==>(try 0 (/ 1 0))
1
@end(example)

Two more hints in designing the implementation:
@begin(itemize)
Be sure to tell @a[user-print] about thunks, so if it is
passed a thunk to print, it will first undelay it.

Try the example in Exercise 4.10 of the text, which
illustrates a easily-made bug in the undelaying strategy.

@end(itemize)

@paragraph(Lab problem 2)

When you think your implementation is working, try defining @a[if] as
a procedure, as shown on page 317 of the text.  Use @a[if] in
defining @a[factorial], as in the pre-lab exercise.  This definition
should work.  Also, try Exercise 4.9 of the text. 

@paragraph(Lab problem 3)

(The following is a modified version of Exercise 4.11 of the course
text.

In chapter 3 we said that streams are like lists, except that the
second argument to @a[cons-stream] is delayed.  However, with
automatically delayed arguments, streams can be identical to lists.
To make lists behave like streams, define @a[cons] as a (nonprimitive)
procedure with delayed arguments.  @a[Cons] will use a new primitive
procedure @a[internal-cons] that does not force its arguments, as do
the other primitive procedures.  Modify @a[apply-primitive-procedure]
to handle @a[internal-cons].  (If you prefer, you can do what Scheme
@a[cons-stream] does and delay only the second argument to @a[cons].)

Test your implementation by trying the following examples:

@begin(example)
**==>(define (integers-from n)
       (cons n (integers-from (+ n 1)))

**==>(define integers (integers-from 1))

**==>(car integers)
1

**==>(car (cdr integers))
2
@end(example)

@paragraph(Lab problem 4)

Do Exercise 4.12 on page 319 of the text.  Only, rather than just
describing the modification in detail, actually implement this in your
interpreter. 

@paragraph(Lab problem 5)

Design and carry out an experiment to test whether the modification
you made in lab problem 4 is working correctly.  Show the experiment,
saying how the evaluator should respond both before and after the
modification in lab problem 4.  (Hint: Think about programs that use
@a[set!].  Also, look at Exercise 3.44 of the text.)  Write a brief
paragraph of explanation.  (Note: illegible or incoherent paragraphs
will be graded unsympathetically, if graded at all.)

@newpage()
@begin(programexample)
;;; This is the code file for problem set 7

;;; It contains the metacircular evaluator, as described in section 4.1
;;; of the course notes, with a few minor modifications.

;;; You should just load this file into Scheme without editing it.  The
;;; new procdures that you will need to modify in order to do the
;;; problem set have been copied into in a separate file for your
;;; convenience.

;;; SETTING UP THE ENVIRONMENT

;;; We initialize the global environment by snarfing a few primitives
;;; from the underlying scheme system, and binding them (to symbols of
;;; the same name).  The actual structure of the environment is
;;; determined by the constructor EXTEND-ENVIRONMENT which is listed
;;; below together with the code that manipulates environments.  If you
;;; want to add more primitives to your evaluator, you can modify the
;;; list PRIMITIVE-NAMES to include more Scheme primitives.

(define primitive-names
  '(+ - * / = < > 1+ -1+ cons car cdr atom? eq? null? not user-print))

(define (setup-environment)
  (define initial-env
    (extend-environment
     primitive-names
     (mapcar (lambda (pname)
               (eval pname user-initial-environment))
             primitive-names)
     nil))
  (define-variable! 'nil nil initial-env)
  (define-variable! 't (not nil) initial-env)
  initial-env)

(define the-global-environment nil)

;;; INITIALIZATION AND DRIVER LOOP

;;; The following code initializes the machine and starts the Lisp
;;; system.  You should not call it very often, because it will clobber
;;; the global environment, and you will lose any definitions you have
;;; accumulated.

(define (initialize-lisp)
  (set! the-global-environment (setup-environment))
  (driver-loop))

;;; Here is the actual driver loop.  It reads in an expression, passes
;;; it to the machine to be evaluated in the global environment, and
;;; prints the result

;;; When/If your interaction with the evaluator bombs out in an error,
;;; you should restart it by calling DRIVER-LOOP.  Note that the driver
;;; uses a prompt of "**==>" to help you avoid confusing typing to the
;;; simulator with typing to the underlying SCHEME interpreter.

(define (driver-loop)
  (newline)
  (princ '**==>)
  (user-print (mini-eval (read) the-global-environment))
  (driver-loop))

;;; We use a special PRINT here, which avoids printing the environment
;;; part of a compound procedure, since the latter is a very long (or
;;; even circular) list.

(define (user-print object)
  (cond
   ((compound-procedure? object)
    (print (list 'compound-procedure
                 (procedure-text object))))
   (else (print object))))


;;; THE GUTS OF THE EVALUATOR

(define (mini-eval exp env)
  (cond ((self-evaluating? exp) exp)
        ((quoted? exp) (text-of-quotation exp))
        ((variable? exp) (lookup-variable-value exp env))
        ((definition? exp) (eval-definition exp env))
        ((assignment? exp) (eval-assignment exp env))
        ((lambda? exp) (make-procedure exp env))
        ((conditional? exp) (eval-cond (clauses exp) env))
        ((application? exp)
         (mini-apply (mini-eval (operator exp) env)
                (list-of-values (operands exp) env)))
        (else (error "Unknown expression type -- EVAL"))))


(define (mini-apply procedure arguments)
  (cond ((primitive-procedure? procedure)
         (apply-primitive-procedure procedure arguments))
        ((compound-procedure? procedure)
         (eval-sequence (procedure-body procedure)
                        (extend-environment
                         (parameters procedure)
                         arguments
                         (procedure-environment procedure))))
        (else (error "Unknown procedure type -- APPLY"))))


(define (list-of-values exps env)
  (cond ((no-operands? exps) '())
        (else (cons (mini-eval (first-operand exps) env)
                    (list-of-values (rest-operands exps)
                                    env)))))
(define (eval-sequence exps env)
  (cond ((last-exp? exps) (mini-eval (first-exp exps) env))
        (else (mini-eval (first-exp exps) env)
              (eval-sequence (rest-exps exps) env))))

(define (eval-cond clist env)
  (cond ((no-clauses? clist) 'nil)
        ((else-clause? (first-clause clist))
         (eval-sequence (action-sequence (first-clause clist))
                        env))
        ((true? (mini-eval (predicate (first-clause clist)) env))
         (eval-sequence (action-sequence (first-clause clist))
                        env))
        (else (eval-cond (rest-clauses clist) env))))



(define (eval-assignment exp env)
  (let ((value (mini-eval (assignment-value exp) env)))
    (set-variable-value! (assignment-variable exp) value env)
    value))

(define (eval-definition exp env)
  (define-variable! (definition-variable exp)
                    (mini-eval (definition-value exp) env)
                    env)
  (definition-variable exp))



;;; Syntax of the language


(define (self-evaluating? exp)
  (number? exp))

(define (text-of-quotation exp) (cadr exp))

(define (variable? exp) (symbol? exp))

(define (assignment? exp)
  (and (not (atom? exp)) (eq? (car exp) 'set!)))

(define (assignment-variable exp) (cadr exp))

(define (assignment-value exp) (caddr exp))

(define (definition? exp)
  (and (not (atom? exp)) (eq? (car exp) 'define)))


(define (definition-variable exp)
  (cond ((variable? (cadr exp))
         (cadr exp))
        (else
         (caadr exp))))


(define (definition-value exp) 
  (cond ((variable? (cadr exp))
         (caddr exp))           
        (else
         (cons 'lambda
               (cons (cdadr exp)     ;Formal parameters
                     (cddr exp))))))

(define (lambda? exp)
  (and (not (atom? exp)) (eq? (car exp) 'lambda)))

(define (conditional? exp)
  (and (not (atom? exp)) (eq? (car exp) 'cond)))

(define (clauses exp) (cdr exp))

(define (no-clauses? clauses) (null? clauses))

(define (first-clause clauses) (car clauses))

(define (rest-clauses clauses) (cdr clauses))

(define (else-clause? clause) (eq? (predicate clause) 'else))

(define (predicate clause) (car clause))

(define (true? x) (not (eq? x nil)))

(define (action-sequence clause) (cdr clause))

(define (last-exp? seq) (eq? (cdr seq) nil))

(define (first-exp seq) (car seq))

(define (rest-exps seq) (cdr seq))

(define (application? exp) (not (atom? exp)))

(define (operator app) (car app))

(define (operands app) (cdr app))

(define (no-operands? args) (eq? args nil))

(define (first-operand args) (car args))

(define (rest-operands args) (cdr args))

(define (last-operand? args)
  (null? (cdr args)))

(define (make-procedure lambda-exp env)
       (list 'procedure lambda-exp env))

(define (compound-procedure? proc)
       (and (not (atom? proc))
            (eq? (car proc) 'procedure)))

(define (procedure-text proc) (cadr proc))

(define (parameters proc) (cadr (cadr proc)))

(define (procedure-body proc) (cddr (cadr proc)))

(define (procedure-environment proc) (caddr proc))


;;; APPLYING PRIMITIVE PROCEDURES

;;; The mechanism for applying primitive procedures is somewhat
;;; different from the one given in the course notes.  We can recognize
;;; primitive procedures (which are all inherited from Scheme) by asking
;;; Scheme if the object we have is a Scheme procedure.

(define (primitive-procedure? p)
  (applicable? p))

;;; To apply a primitive procedure, we ask the underlying Scheme system
;;; to perform the application.  (Of course, an implementation on a
;;; low-level machine would perform the application in some other way.)

(define (apply-primitive-procedure p args)
  (apply p args))


;;; ENVIRONMENTS


;;; Environments are represented as association lists, as described in
;;; section 7.4.4 of the notes.

(define the-empty-environment '())

(define (add-binding-pair var val env)
  (cons (list var val) env))

(define (lookup-variable-value var env)
  (let ((bp (binding-pair var env)))
    (cond ((null? bp) (error "Unbound variable" var))
          (else (cadr bp)))))

(define (set-variable-value! var val env)
  (let ((bp (binding-pair var env)))
    (cond ((null? bp) (error "Unbound variable" var))
          (else (set-car! (cdr bp) val)))))

(define (define-variable! var val env)
  (set-car! env
            (cons (list var val)
                  (car env))))

(define (extend-environment variables values base-env)
  (define (pair-up variables values)
    (cond ((null? variables)
           (cond ((null? values) '())
                 (t
                  (error "Too many arguments supplied"))))
          ((null? values)
           (error "Too few arguments supplied"))
          (else (cons (list (car variables) (car values))
                      (pair-up (cdr variables)
                               (cdr values))))))
  (cons (pair-up variables values) base-env))

(define (binding-pair var env)
  (define (scan pairlist)
    (cond ((null? pairlist)
           (cond ((null? (cdr env)) 'nil)
                 (else (binding-pair var (cdr env)))))
          ((eq? var (caar pairlist)) (car pairlist))
          (else (scan (cdr pairlist)))))
  (scan (car env)))

@end(programexample)
@newpage()
@begin(programexample)
;;; This is the modifications file for problem set 8.  It contains the
;;; procedures from the evaluator that you will need to modify in order
;;; to do the assignment.  Of course, you will also have to write
;;; additional procedures from scratch.

;;; WARNING:  Depending on how you do the problem set, there may be
;;; other procedures from the evaluator that you may want to modify.
;;; You may want to ask a TA about how to move blocks of code between
;;; editor buffers.

(define (user-print object)
  (cond
   ((compound-procedure? object)
    (print (list 'compound-procedure
                 (procedure-text object))))
   (else (print object))))


(define (mini-eval exp env)
  (cond ((self-evaluating? exp) exp)
        ((quoted? exp) (text-of-quotation exp))
        ((variable? exp) (lookup-variable-value exp env))
        ((definition? exp) (eval-definition exp env))
        ((assignment? exp) (eval-assignment exp env))
        ((lambda? exp) (make-procedure exp env))
        ((conditional? exp) (eval-cond (clauses exp) env))
        ((application? exp)
         (mini-apply (mini-eval (operator exp) env)
                (list-of-values (operands exp) env)))
        (else (error "Unknown expression type -- EVAL"))))


(define (mini-apply procedure arguments)
  (cond ((primitive-procedure? procedure)
         (apply-primitive-procedure procedure arguments))
        ((compound-procedure? procedure)
         (eval-sequence (procedure-body procedure)
                        (extend-environment
                         (parameters procedure)
                         arguments
                         (procedure-environment procedure))))
        (else (error "Unknown procedure type -- APPLY"))))


(define (apply-primitive-procedure p args)
  (apply p args))
@end(programexample)


