Metacircular evaluator #1

Accept => clause in COND:

This is a change in the COND special form.  All special forms are
handled by subprocedures of EVAL; this one by EVAL-COND.

(define (eval-cond clist env)
  (cond ((no-clauses? clist) nil)
	((else-clause? (first-clause clist))
	 (eval-sequence (actions (first-clause clist)) env))
	(ELSE (LET ((VAL (EVAL (PREDICATE (FIRST-CLAUSE CLIST)) ENV)))
		(IF VAL
		    (IF (ARROW-CLAUSE? (FIRST-CLAUSE CLIST))
			(APPLY (EVAL (ARROW-PROC (FIRST-CLAUSE CLIST)) ENV)
			       (LIST VAL))
			(EVAL-SEQUENCE (ACTIONS (FIRST-CLAUSE CLIST)) ENV))
		    (EVAL-COND (REST-CLAUSES CLIST) ENV) ))) ))

(DEFINE (ARROW-CLAUSE? CLAUSE)
  (EQ? (CADR CLAUSE) '=>))

(DEFINE ARROW-PROC CADDR)



Metacircular evaluator #2

(a) Evaluate args left-to-right.

This is a change to the rule that says "to evaluate an expression,
begin by evaluating the subexpressions."  That's in EVAL, more
precisely in LIST-OF-VALUES:

(define (list-of-values exps env)
  (cond ((no-operands? exps) '())
	(else (LET ((FIRST (EVAL (FIRST-OPERAND EXPS) ENV)))
		(CONS FIRST
		      (list-of-values (rest-operands exps) env) )))))

(b) Does this version always evaluate the operator first?

No.  That ordering is determined in EVAL itself.  To fix the order,
we'd have to change

    (apply (eval (operator exp) env)
	   (list-of-values (operands exp) env) )

to

    (let ((proc (eval (operator exp) env)))
      (apply proc (list-of-values (operands exp) env) ) )

(c) When does it matter?

> (define x 3)
> ( (sequence (set! x 50) 1+) x)

prints 51 if the operator is evaluated first, but prints 4 if the
operand is evaluated first.  (Of course there are many other possible
answers, and of course the answer has to involve a non-functional
computation.)



Metacircular evaluator #3

The change has to do with assigning actual argument values to formal
parameters.  This is the job of apply, specifically in
extend-environment, more specifically in make-frame.  Changes below
are in capital letters.

(define (extend-environment variables values base-env)
  (adjoin-frame (make-frame variables values BASE-ENV) base-env))

(define (make-frame variables values BASE-ENV)
  (cond ((and (null? variables) (null? values)) '())
	((null? variables)
	 (error "Too many values supplied" values))
	((null? values)
	 (error "Too few values supplied" variables))
	((LIST? (CAR VARIABLES))
	 (IF (APPLY (EVAL (CAAR VARIABLES) BASE-ENV)
		    (LIST (CAR VALUES)))
	     (CONS (MAKE-BINDING (CADAR VARIABLES) (CAR VALUES))
		   (MAKE-FRAME (CDR VARIABLES) (CDR VALUES) BASE-ENV))
	     (ERROR "WRONG ARGUMENT TYPE" (CAR VALUES)) ))
	(else
	 (cons (make-binding (car variables) (car values))
	       (make-frame (cdr variables) (cdr values) BASE-ENV) )) ))



Metacircular evaluator #4

(a) Eval or apply?

The modification must be made in EVAL.  Many people said "this change
is about arguments to procedures, and it's apply's job to deal with
arguments."  That's an oversimplified view of things.  When a procedure
is invoked, there are two steps in dealing with the arguments:
	1.  Convert the actual argument EXPRESSIONS into actual
	    argument VALUES.
	2.  Bind the formal parameters to the actual argument values.
The second of those is apply's job, but the first requires us to
evaluate subexpressions: EVAL's job.

The change we are making is part of the evaluation of actual argument
expressions.  In the example, A-VALUE and B-VALUE are actual argument
expressions.  ARGLIST is a sort-of actual argument expression whose
value is a bunch of arguments, rather than a single argument.  Turning
the name ARGLIST into the list that it represents (in other words,
evaluating the symbol ARGLIST) is done in eval.

Scoring: 2 points, all or nothing.  A couple of people argued that
both answers are correct because eval and apply are mutually
recursive.  True, but, for example, eval-definition is invoked
directly by eval, whereas make-frame is never invoked by eval except
through an invocation of apply.

(b) Where specifically?

LIST-OF-VALUES.  This is the procedure that turns all the
actual argument expressions into actual argument values.  It's
where we cdr down the list of actual argument expressions, and
it's where we have to notice the case of an improper list.

Scoring: 2 points.  Part credit (1 point) if you said somewhere
else and actually got it to work; this was rare but possible, if
you have eval take over the job of list-of-values.  Also part
credit if you mentioned two procedures, one of which was list-of-values.
No credit if you mentioned three or more procedures, which we
interpreted as wild guessing.

(c) The actual modification:

(define (list-of-values exps env)
  (cond ((no-operands? exps) '())
	((SYMBOL? EXPS) (EVAL EXPS ENV))
        (else (cons (eval (first-operand exps) env)
                    (list-of-values (rest-operands exps)
                                    env)))))

The part in capital letters is the only change needed.

Okay, how does it work?  If we are looking at an improper list (that
is, one in which there is a pair whose cdr isn't a list), sooner or
later the recursion will give us that cdr as the argument.  We
recognize an improper list by noticing that EXPS is a symbol, not a
pair or nil.  (Several people said ATOM? instead of SYMBOL? here.  We
accepted that answer, because it works if you put the clause after the
no-operands one, but it's not as precise because nil is an atom, but
also a proper list.)

Suppose we've found the symbol.  (This is the symbol ARGLIST in the
example.  Too many people said something about 'ARGLIST in their code!
Again, the whole idea of programming is to write programs that work in
general situations, not just solve one example problem.)  What we need
to do is look up the value of the variable whose name is that symbol.
We do that by EVAL-ing the symbol.  (We could uselookup-variable-value
instead, since we know that the expression is a symbol.)  Then what?
The result is a list of actual argument values, just what we wanted!

Two common mistakes are worth special mention.  The first is to try
to catch the improper list one step too soon in the recursion, with
an expression like (SYMBOL? (CDR EXPS)).  This is not necessarily a
total disaster, because you can almost recover by consing the evaluated
car onto the evaluated cdr.  But nobody who tried this got it exactly
right, and in any case it would fail for an example like (FOO . ARGLIST)
in which there is no individual argument expression to be the car of
a pair.

Another common mistake was to say (LIST-OF-VALUES (EVAL EXPS ENV)).
This comes from a misunderstanding of the problem.  The value of the
variable ARGLIST is supposed to be a list of actual argument values,
not a list of actual argument expressions.

Those were forgivable errors; if you made either of those mistakes
it meant that you had a pretty good understanding of the problem and
of the metacircular evaluator.  There were too many more serious errors
to catalog, but we'll mention two categories.

One popular category was to write something that would work only for
the particular example we gave in the problem:
	* making FOO a special form
	* checking for 'ARGLIST
	* requiring exactly two arguments before the dot
Again, this misses out on the fundamental idea of computer programming.

The other was to look for an improper list with something like
(EQ? (CAR EXPS) '|.|) -- checking for a dot in the list structure.
This indicates a misunderstanding of how the Lisp reader works.
When you type something like (2 . 3) you are not creating a list
of three elements: two, dot, three.  You are creating a single
pair whose car is 2 and whose cdr is 3.  Similarly, (a b . c) is
two pairs; the first has A as its car and a second pair as its
cdr, while that second pair has B as its car and C as its cdr.

Scoring: 2 points if it's right; 1 point if it's right except for
some minor problems.