about summary refs log tree commit diff stats
path: root/js/games/nluqo.github.io/~bh/61a-pages/Solutions/week12
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2023-08-23 07:52:19 -0400
committerelioat <elioat@tilde.institute>2023-08-23 07:52:19 -0400
commit562a9a52d599d9a05f871404050968a5fd282640 (patch)
tree7d3305c1252c043bfe246ccc7deff0056aa6b5ab /js/games/nluqo.github.io/~bh/61a-pages/Solutions/week12
parent5d012c6c011a9dedf7d0a098e456206244eb5a0f (diff)
downloadtour-562a9a52d599d9a05f871404050968a5fd282640.tar.gz
*
Diffstat (limited to 'js/games/nluqo.github.io/~bh/61a-pages/Solutions/week12')
-rw-r--r--js/games/nluqo.github.io/~bh/61a-pages/Solutions/week121008
1 files changed, 1008 insertions, 0 deletions
diff --git a/js/games/nluqo.github.io/~bh/61a-pages/Solutions/week12 b/js/games/nluqo.github.io/~bh/61a-pages/Solutions/week12
new file mode 100644
index 0000000..7b8c5a3
--- /dev/null
+++ b/js/games/nluqo.github.io/~bh/61a-pages/Solutions/week12
@@ -0,0 +1,1008 @@
+CS 61A       Week 12 solutions
+
+LAB EXERCISES
+=============
+
+3.  Why doesn't make-procedure call eval?
+
+Because none of the arguments to lambda should be evaluated.
+In particular, the expressions that make up the body of the procedure are
+not evaluated until the procedure is *invoked*!
+
+
+4.1, left-to-right
+
+(define (list-of-values exps env)       ;; left to right
+  (if (no-operands? exps)
+      '()
+      (let ((left (eval (first-operand exps) env)))
+	(cons left (list-of-values (rest-operands exps) env)))))
+
+(define (list-of-values exps env)       ;; right
+  (if (no-operands? exps)
+      '()
+      (let ((right (list-of-values (rest-operands exps) env)))
+	(cons (eval (first-operand exps) env) right))))
+
+
+4.2, Louis reordering
+
+(a)  The trouble is that APPLICATION? cheats.  The book has
+
+(define (application? exp) (pair? exp))
+
+It really should be something like
+
+(define (application? exp)
+  (and (pair? exp)
+       (not (member (car exp) '(quote set! define if lambda begin cond)))))
+
+They get away with the shorter version precisely because EVAL doesn't
+call APPLICATION? until after it's checked for all the possible special
+forms.  Louis (quite reasonably, I think) wants to rely on APPLICATION?
+behaving correctly no matter when it's called.
+
+(b)  All we are changing is the syntax of an application, so we
+change the procedures that define the "application" abstract data type.
+These are on page 372 of the text.  The new versions are:
+
+(define (application? exp)
+  (tagged-list? exp 'call))
+
+(define (operator exp) (cadr exp))
+
+(define (operands exp) (cddr exp))
+
+
+4.4 AND and OR special forms
+
+The book suggests two solutions: make them primitive special forms
+or make them derived expressions.  We'll do both.
+
+As primitive special forms:
+
+Change the COND clause in EVAL by adding
+
+  (cond ...
+	((and? exp) (eval-and exp env))
+	((or? exp) (eval-or exp env))
+	...)
+
+(define (eval-and exp env)
+  (define (iter tests)
+    (cond ((null? tests) #t)
+	  ((null? (cdr tests)) (eval (car tests) env))
+	  ((true? (eval (car tests) env)) (iter (cdr tests)))
+	  (else #f)))
+  (iter (cdr exp)))
+
+(define (eval-or exp env)
+  (define (iter tests)
+    (if (null? tests)
+	#f
+	(let ((result (eval (car tests) env)))
+	  (if (true? result)
+	      result
+	      (iter (cdr tests))))))
+  (iter (cdr exp)))
+
+Now for the derived expression technique.  Modify the COND clause
+in EVAL this way instead:
+
+  (cond ...
+	((and? exp) (eval (and->if (cdr exp)) env))
+	((or? exp) (eval (or->if (cdr exp)) env))
+	...)
+
+(define (and->if exps)
+  (cond ((null? exps) #t)
+	((null? (cdr exps)) (car exps))
+	(else (make-if (car exps)
+		       (and->if (cdr exps))
+		       #f))))
+
+(define (or->if exps)
+  (if (null? exps)
+      #f
+      (make-if (car exps)
+	       (car exps)
+	       (or->if (cdr exps)))))
+
+This version is elegant but has the disadvantage that you end up
+computing the first true value twice.
+
+
+4.5  Cond => notation
+
+(define (expand-clauses clauses)
+  (if (null? clauses)
+      'false
+      (let ((first (car clauses))
+	    (rest (cdr clauses)))
+	(if (cond-else-clause? first)
+	    (if (null? rest)
+		(sequence->exp (cond-actions first))
+		(error "..."))
+	    (IF (COND-ARROW-CLAUSE? FIRST)
+		(LIST (MAKE-LAMBDA '(COND-FOO)
+				   (MAKE-IF 'COND-FOO
+					    (LIST (COND-ARROW-DOER FIRST)
+						  'COND-FOO)
+					    (EXPAND-CLAUSES REST)))
+		      (COND-PREDICATE FIRST))
+		(make-if (cond-predicate first)
+			 (sequence->exp (cond-actions first))
+			 (expand-clauses rest)))))))
+
+(define (cond-arrow-clause? clause)
+  (and (pair? clause)
+       (= (length clause) 3)
+       (eq? (cadr clause) '=>)))
+
+(define (cond-arrow-doer clause)
+  (caddr clause))
+
+This may be a little confusing.  What it does is to turn a clause like
+
+        (test => recipient)
+
+into
+
+        ((lambda (cond-foo)
+	   (if cond-foo
+	       (recipient cond-foo)
+	       <expanded-rest-of-clauses>))
+	 test)
+
+Using the name cond-foo here is a kludge, because what if the user
+has used the same name for some other purpose within the clause?
+The right thing would be to generate an otherwise-untypable symbol
+each time.  But this is complicated enough already.
+
+By the way, this is really trying to do
+
+        (let ((cond-foo test))
+	  (if ...))
+
+but we don't yet have LET in the metacircular Scheme.
+
+It might be easier to do this by abandoning the whole idea of
+cond->if and just implementing cond directly.
+
+
+5b.  In Logo there are no internal definitions; all procedures are global.
+So we need a situation with two procedures, one of which calls the other:
+
+to outer :var
+inner
+end
+
+to inner
+print :var
+end
+
+? outer 23
+23
+
+To see that this result is different from what would happen with lexical
+scope, try the same example in Scheme:
+
+(define (outer var)
+  (inner))
+
+(define (inner)
+  (print var))
+
+> (outer 23)
+Error -- unbound variable: var
+
+(Or you could define a global variable var whose value is something other
+than 23, and then (outer 23) would print that other value.
+
+
+5c.
+
+Logo " is like Scheme ' -- it's the quoting symbol.  But in Logo it is used
+only with words, not with lists, and there is no QUOTE special form which the
+quotation character abbreviates.
+
+Logo [ ] are like '( ) in Scheme -- the brackets both delimit and quote a
+list.  But within a list, brackets are used to delimit sublists, and don't
+imply an extra level of quotation, so Logo [a [b c] d] means '(a (b c) d),
+not '(a '(b c) d).  So, how do you get the effect of Scheme's ( ) without
+quotation?  In Scheme that means to call a procedure; in Logo you don't
+need any punctuation to call a procedure!  You just give the procedure name
+and its arguments.  But in Logo you *can* use parentheses around a procedure
+call just as you would in Scheme.
+
+Logo : means that you want the value of the variable whose name follows the
+colon.  In Scheme the name by itself means this -- if you want the value of
+variable X, you just say X.  The reason this doesn't work in Logo is that
+in Logo procedures aren't just another data type, and a procedure name isn't
+just the name of a variable whose value happens to be a procedure.  (In other
+words, Logo procedures are not first-class.)  In Logo there can be a procedure
+and a variable with the same name, so FOO means the procedure and :FOO means
+the variable.
+
+
+HOMEWORK
+========
+
+4.3  data-directed eval
+
+The table itself could be done in several ways; perhaps the easiest
+is to use the built-in table from chapter 2.  So we say:
+
+(put 'quote 'eval text-of-quotation)
+(put 'define 'eval eval-definition)
+(put 'set! 'eval eval-assignment)
+
+Where the original eval does something other than (foo exp env) we
+have to write an interface procedure.  For example:
+
+(define (eval-lambda exp env)
+  (make-procedure (lambda-parameters exp) (lambda-body exp) env))
+
+(put 'lambda 'eval eval-lambda)
+
+
+(define (eval exp env)
+  (cond ((self-evaluating? exp) exp)
+	((variable? exp) (lookup-variable-value exp env))
+	(else (let ((form (get (operator exp) 'eval)))
+		 (if form       	;; IT'S A SPECIAL FORM
+		     (form exp env)	;; SO form IS THE PROCEDURE TO CALL
+		     (apply (eval (operator exp) env)
+			    (list-of-values (operands exp) env) ))))))
+
+The first two COND clauses deal with atomic expressions: numbers (which
+are self-evaluating) and symbols (which represent variables).  If the
+expression is neither of those, then it's a list, and we look at its
+CAR.  We look that up in the table; if we find it, the expression is a
+special form, and we invoke the particular procedure that knows about
+that special form.  Otherwise, it's a regular procedure.
+We're neglecting various kinds of errors that might occur with mal-formed
+input.
+
+We also have to rewrite text-of-quotation so that it accepts an extra
+input, the environment, even though it doesn't need it:
+
+(define (text-of-quotation exp env)
+  (cadr exp))
+
+And we have to write a new "front end" to cond->if:
+
+(define (eval-cond exp env)
+  (eval (cond->if exp) env))
+
+and put that in the table.
+
+It would also be possible to include the atomic expressions in the
+general data-directed mechanism by assigning them implicit types just as
+we assigned Scheme numbers an implicit type in exercise 2.78, page 193:
+
+(define (expression-type exp)
+  (cond ((self-evaluating? exp) '(() SELF-EVALUATING))
+	((symbol? exp) '(() SYMBOL))
+	((pair? exp) (car exp))
+	(else (error "Unknown expression type" exp))))
+
+(define (eval exp env)
+  (let ((handler (get (expression-type exp) 'eval)))
+    (if handler
+	(handler exp env)
+	(apply (eval (operator exp) env)
+	       (list-of-values (operands exp) env)))))
+
+(put '(() self-evaluating) 'eval (lambda (exp env) exp))
+(put '(() symbol) 'eval lookup-variable-value)
+
+The reason for using (() SYMBOL) instead of just SYMBOL as the type tag
+is that otherwise we'd get in trouble if an expression tried to call a
+procedure named SYMBOL.  These type tags aren't valid Scheme expressions,
+so they shouldn't get us in trouble.
+
+
+4.6  Implementing LET   
+
+;; In eval's big cond we put
+
+	((let? exp) (eval (let->combination exp) env))
+
+;; Now for the guts of the problem:
+
+(define (let->combination exp)
+  (cons (make-lambda (let-formals exp)
+		     (let-body exp))
+	(let-actuals exp)))
+
+;; And now for the data abstraction stuff:
+
+(define (let? exp)
+  (tagged-list? exp 'let))
+
+(define (let-formals exp)
+  (map car (cadr exp)))
+
+(define (let-actuals exp)
+  (map cadr (cadr exp)))
+
+(define (let-body exp)
+  (cddr exp))
+
+
+Please note that this problem becomes MUCH easier if you ruthlessly separate
+the semantics (let->combination) from the mickey mouse work of extracting
+the syntactic components.  I actually wrote this in the order in which it
+appears here; in essence I solved the problem completely before I thought at
+all about syntactic issues.
+
+
+4.7 Implementing Let*
+
+(define (let*->nested-lets exp)
+  (if (null? (let-bindings exp))
+      (make-let '() (let-body exp))
+      (make-let (list (car (let-bindings exp)))
+		(list (make-let* (cdr (let-bindings exp))
+				 (let-body exp))))))
+
+(define (let-bindings exp)
+  (cadr exp))
+
+(define (make-let bindings body)
+  (cons 'let (cons bindings body)))
+
+(define (make-let* bindings body)
+  (cons 'let* (cons bindings body)))
+
+I'm cheating slightly by using LET-BODY for a LET* expression instead
+of inventing a whole new abstract data type.  In principle someone
+might want to change Scheme so that the syntax of LET* looks different
+from the syntax of LET.
+
+
+4.10 new syntax
+
+Okay, let's make the syntax of IF look like it does in those other bad
+languages.  (After all, any change we make to Scheme's syntax *has* to make
+it worse!)  The new syntax will be (if ... then ... else ...).
+
+(define (if? exp)
+  (and (tagged-list? exp 'if)
+       (eq? (caddr exp) 'then)
+       (or (= (length exp) 4)
+	   (eq? (list-ref exp 4) 'else))))
+
+(define (if-predicate exp) (cadr exp))
+
+(define (if-consequent exp) (cadddr exp))
+
+(define (if-alternative exp) (list-ref exp 5))
+
+Of course you can do lots of other changes too, so if you're copying
+last semester's answers next semester, the reader will be suspicious
+if you come up with this choice!  :-)
+
+
+4.11  changed frame representation
+
+(define (make-frame variables values)
+  (attach-tag 'frame (map cons variables values)))
+
+(define (frame-variables frame)
+  (map car (contents frame)))
+
+(define (frame-values frame)
+  (map cdr (contents frame)))
+
+(define (add-binding-to-frame! var val frame)
+  (set-cdr! frame (cons (cons var val) (contents frame))))
+
+As explained in footnote 14 on page 378, the procedures lookup-variable-value,
+set-variable-value!, and define-variable! aren't just above-the-line users of
+the frame ADT, because the latter two use SET-CAR! to modify frames.
+Lookup-variable-value could actually work exactly as written, but the others
+have to be changed, and that one should also be changed, to use ASSOC in
+their SCAN internal procedures.  Basically these will look like the table
+procedures from chapter 3:
+
+(define (lookup-variable-value var env)
+  (define (env-loop env)
+    (DEFINE (SCAN ALIST)
+      (LET ((RESULT (ASSOC VAR ALIST)))
+	(IF RESULT
+	    (CDR RESULT)
+	    (ENV-LOOP (ENCLOSING-ENVIRONMENT ENV)))))
+    (if (eq? env the-empty-environment)
+	(error "Unbound variable" var)
+	(let ((frame (first-frame env)))
+	  (SCAN (CONTENTS FRAME)))))
+  (env-loop env))
+
+(define (set-variable-value! var val env)
+  (define (env-loop env)
+    (DEFINE (SCAN ALIST)
+      (LET ((RESULT (ASSOC VAR ALIST)))
+	(IF RESULT
+	    (SET-CDR! RESULT VAL)
+	    (ENV-LOOP (ENCLOSING-ENVIRONMENT ENV)))))
+    (if (eq? env the-empty-environment)
+	(error "Unbound variable -- SET!" var)
+	(let ((frame (first-frame env)))
+	  (SCAN (CONTENTS FRAME)))))
+  (env-loop env))
+
+(define (define-variable! var val env)
+  (let ((frame (first-frame env)))
+    (DEFINE (SCAN ALIST)
+      (LET ((RESULT (ASSOC VAR ALIST)))
+	(IF RESULT
+	    (SET-CDR! RESULT VAL)
+	    (ADD-BINDING-TO-FRAME! VAR VAL FRAME))))
+    (SCAN (CONTENTS FRAME))))
+
+If I hadn't attached a tag to the frames, this would be harder.
+I wouldn't be able to have an add-binding-to-frame! procedure
+because there wouldn't be anything in an empty frame to mutate.
+Instead, define-variable! would have to get more complicated.
+
+
+4.13  make-unbound   
+
+First, about the design issues:  I see three possibilities.  You can
+require that the symbol be bound in the current environment and remove
+that binding only; you can remove the nearest single binding; or you can
+remove all bindings of that symbol.  Perhaps the best solution in any case
+where it's not obvious what the right semantics is would be to provide
+all three versions: unbind-this-frame, unbind-nearest, and unbind-all.
+That way the user can decide for herself what best suits the application
+at hand.  Failing that, I vote for the second choice: removing the nearest
+binding.  Here's why.  First of all, the third version can be written in
+terms of the second:
+
+(define (unbind-all sym)
+  (cond ((bound? sym)
+	 (unbind-nearest sym)
+	 (unbind-all sym))
+	(else '())))
+
+(This assumes we have a predicate bound? that returns true if there is
+an accesible binding for the symbol.  If we provide any form of unbinding
+we should probably provide that too.)  But the second can't be written in
+terms of the third.  So if we're only having one we should have the more
+flexible one.  I rule out the first (current frame only) because I can
+easily imagine wanting to write a procedure like
+
+(define (cleanup)
+  (make-unbound 'a)
+  (make-unbound 'b)
+  (make-unbound 'c))
+
+that removes global variables at the end of a computation, but this
+wouldn't be possible under the first option.  (Why not?)
+
+I have also implicitly decided another design question: should this be
+a special form that requires an unevaluated symbol, like set!, or should
+it be an ordinary procedure whose actual parameter is evaluated?  In
+order to make things like unbind-all (above) work, it should be an ordinary
+procedure.  (What I want to get unbound is the actual argument to
+unbind-all, not the symbol "sym" itself.)  Then again, I think set! should
+be an ordinary procedure, too, so perhaps you're asking the wrong person.
+
+Trouble is, we can't REALLY make make-unbound an ordinary procedure
+because it has to have access to the environment.  If Scheme were
+dynamically scoped, any procedure in the evaluator could just make a
+free reference to "env" to get the current user environment, but as it
+is we have to have eval treat make-unbound specially.  So we'll make 
+it a special form but still have it evaluate everything.
+
+(define (eval-make-unbound exp env)
+  (define (unbind-in-frame sym frame)
+    (define (remove-not-first-binding vars vals)
+      (if (eq? sym (cadr vars))
+	  (begin (set-cdr! vars (cddr vars))
+		 (set-cdr! vals (cddr vals)))
+	  (remove-not-first-binding (cdr vars) (cdr vals))))
+    (if (eq? sym (car (frame-variables frame)))
+	(begin (set-car! frame (cdr (frame-variables frame)))
+	       (set-cdr! frame (cdr (frame-values frame))))
+	(remove-not-first-binding (frame-variables frame)
+				  (frame-values frame))))
+  (define (env-iter sym env)
+    (cond ((eq? env the-empty-environment) 'okay)
+	  ((memq sym (frame-variables (first-frame env)))
+	   (unbind-in-frame sym (first-frame env)))
+	  (else (env-iter sym (enclosing-environment env)))))
+  (env-iter (eval (cadr exp) env) env))
+
+This is rather messier than one might wish, because if the binding in
+question is the first one in a frame, we have to remove it differently from
+if it's not the first in a frame.  In the first case we mutate the header
+pair of the frame; in the second case we splice elements out of two lists.
+Had this evaluator been written with unbinding in mind, they might have
+picked a different data structure.  Env-iter looks for the first frame in
+which the symbol is bound, then unbinds it in that frame.  Unbind-in-frame
+first checks the first binding specially, then uses remove-not-first-binding
+to check the other bindings.
+
+Strictly speaking, I should have made mutators for the frame
+abstraction.  The obvious choice would be set-frame-variables! and
+set-frame-values!, but actually that only makes sense if we know that
+the frame is represented as two parallel lists.  If the frame is
+represented as an a-list, as in exercise 4.11, then a better choice
+would be set-frame-bindings!.  So the really right thing, to keep
+the abstraction barrier solid, is to have a mutator frame-remove-binding!
+that would be like the unbind-in-frame part of the code above.  It would
+be different for different representations, but would have the same
+effect above the abstraction barrier.
+
+Finally, we have to modify eval, adding
+
+  ((make-unbound? exp) (eval-make-unbound exp env))
+
+to the big cond.
+
+(define (make-unbound? exp)
+  (tagged-list? exp 'make-unbound))
+
+
+
+4.14 why doesn't map work?
+
+This question is about level confusion.  Let's talk about meta-Scheme,
+the one implemented by the metacircular evaluator, and under-Scheme, the
+regular Scheme in which the MCE is written.
+
+Eva defines MAP in meta-Scheme.  In particular, when MAP tries to invoke
+a meta-Scheme procedure for each element of the list, it's doing a
+meta-Scheme invocation.
+
+Louis uses the MAP that's defined in under-Scheme.  When he calls MAP,
+he is giving it a meta-Scheme procedure as its first argument, but it's
+expecting an under-Scheme procedure.  From the point of view of under-Scheme,
+a meta-Scheme procedure isn't a procedure at all -- it's a list whose car
+is the word PROCEDURE.
+
+
+4.15 the halting problem
+
+This is the most famous problem in automata theory, the most elegant proof that
+some things can't be done no matter how sophisticated our computers become.
+The proof was first given using the "Turing machine," an abstract machine
+that's used only for proving theorems.  But the same idea works in any
+formal system that's capable of representing a procedure as data; the key
+element of the proof is the fact that the hypothetical HALTS? is a
+higher-order function.
+
+Suppose that (HALTS? TRY TRY) returns #T.  Then when we call (TRY TRY)
+it says, after argument substitution,
+
+        (if (halts? try try)
+	    (run-forever)
+	    'halted)
+
+But this will run forever, and so (TRY TRY) runs forever, and so
+(HALTS? TRY TRY) should have returned #F.
+
+Similarly, suppose that (HALTS? TRY TRY) returns #F.  Then (TRY TRY)
+turns into the same IF expression shown above, but this time the
+value of that expression is the word HALTED -- that is, it halts.
+So (HALTS? TRY TRY) should have returned #T.
+
+
+4.22  LET in analyzing evaluator
+
+This is easy, given the hint about 4.6.  We don't have to change the
+procedure LET->COMBINATION we wrote for that exercise; since it deals
+entirely with the expression, and not with the values of variables,
+all of its work can be done in the analysis phase.  All we do is
+change this COND clause in EVAL:
+
+	((let? exp) (eval (let->combination exp) env))
+
+to this COND clause in ANALYZE:
+
+	((let? exp) (analyze (let->combination exp)))
+
+
+4.23  Efficiency of analyze-sequence
+
+For a sequence with just one expression, the book's version does the
+following analysis:  First, the body of analyze-sequence is the LET.
+Suppose that the result of analyzing the one expression is PROC.
+Then the variable PROCS will have as its value a list whose only
+element is PROC.  That's not null, so (still in the analysis part)
+we call (LOOP PROC '()).  In LOOP, since (in this case) REST-PROCS
+is null, LOOP just returns PROC.  So if the analysis of EXP gives
+PROC, then the analysis of (BEGIN EXP) also gives PROC.
+
+In the same one-expression case, Alyssa's version returns
+	(lambda (env) (execute-sequence (list PROC) env))
+So every time this execution procedure is called, execute-sequence
+will check that (cdr procs) is empty, which it is, and then
+calls PROC with the environment as its argument.  This test of
+(null? (cdr procs)) is done for every execution, whereas in the
+book's version it was done just once.
+
+How about the two-expression case.  Suppose that the analysis of
+EXP1 gives PROC1, and the anaylsis of EXP2 gives PROC2.  The book's
+version will call, in effect, (loop PROC1 (list PROC2)).  This
+in turn calls (sequentially PROC1 PROC2), which returns
+	(lambda (env) (PROC1 env) (PROC2 env))
+as the execution procedure.  (There is a recursive call to LOOP,
+but it doesn't change the result, because this time the second
+argument is null.)
+
+Alyssa's version makes the execution procedure be
+	(lambda (env) (execute-sequence (list PROC1 PROC2) env)))
+which in effect means
+	(lambda (env)
+	  (cond ((null? (list PROC2)) ...)
+		(else (PROC1 env)
+		      (cond ((null? '()) (PROC2 env)) ...))))
+Each time this is executed, we do two unnecessary checks for
+the nullness of a list -- unnecessary because we already knew
+while doing the analysis how many expressions are in the sequence.
+
+
+4.24  How fast?
+
+Hint:  You'll get the most dramatic results when an expression
+is evaluated over and over, i.e., with a recursive procedure.
+
+
+
+2.  Type checking
+
+When we define a procedure, we don't even look at the parameter
+list; it's just stored as part of the procedure.  That doesn't
+need to be changed.  When do we have to check the type?  We do it
+when we're invoking a procedure, as part of the process of
+binding names to values.  This happens in extend-environment
+and make-frame.  The only change to extend-environment is that it
+has to supply the environment that we're extending to make-frame,
+because make-frame will have to look up the type predicates:
+
+(define (extend-environment vars vals base-env)
+  (if (= (length vars) (length vals))
+      (cons (make-frame vars vals BASE-ENV) base-env)
+      (if (< (length vars) (length vals))
+	  (error "Too many arguments supplied" vars vals)
+	  (error "Too few arguments supplied" vars vals))))
+
+Make-frame, which was trivial before this change, now has some
+real work to do:
+
+(define (make-frame variables values BASE-ENV)
+  (DEFINE (TYPE-CHECK VAR VAL)
+    (IF (AND (PAIR? VAR)
+	     (NOT (APPLY (EVAL (CAR VAR) BASE-ENV)
+			 (LIST VAL))))
+	(ERROR "WRONG ARGUMENT TYPE" VAL)))
+  (DEFINE (SCAN VARS VALS)
+    (COND ((NULL? VARS) #T)
+	  (ELSE (TYPE-CHECK (CAR VARS) (CAR VALS))
+		(SCAN (CDR VARS) (CDR VALS)))))
+  (SCAN VARIABLES VALUES)
+  (cons (JUST-NAMES variables) values))
+
+(DEFINE (JUST-NAMES VARS)
+  (COND ((NULL? VARS) '())
+	((PAIR? (CAR VARS))
+	 (CONS (CADAR VARS) (JUST-NAMES (CDR VARS))))
+	(ELSE (CONS (CAR VARS) (JUST-NAMES (CDR VARS))))))
+
+Another approach would be to try to modify the procedure as it's being
+created (therefore, in make-procedure, called from eval) so that the type
+checks become part of the procedure's body.  This can be done, but it's
+quite tricky to get it right.  For example, in what environment should the
+names of the type predicates be looked up?
+
+It's a real misunderstanding of the problem to write a solution in which
+specific type predicates such as INTEGER? are built into the evaluator.
+If there's a type checking system, it should work for user-defined types
+as well as for primitive types.  For example, I should be able to say
+that an argument must be a prime number, or must be a three-letter word.
+
+  
+
+Extra for Experts
+=================
+
+4.16
+
+(a)
+
+(define (lookup-variable-value var env)
+  (define (env-loop env)
+    (define (scan vars vals)
+      (cond ((null? vars)
+             (env-loop (enclosing-environment env)))
+            ((eq? var (car vars))
+             (LET ((RESULT (car vals)))			  ;; ***
+	       (IF (EQ? RESULT '*UNASSIGNED*)		  ;; ***
+		   (ERROR "UNBOUND VARIABLE" (CAR VARS))  ;; ***
+		   RESULT)))				  ;; ***
+            (else (scan (cdr vars) (cdr vals)))))
+    (if (eq? env the-empty-environment)
+        (error "Unbound variable" var)
+        (let ((frame (first-frame env)))
+          (scan (frame-variables frame)
+                (frame-values frame)))))
+  (env-loop env))
+
+
+(b)
+
+(define (scan-out-defines body)
+  (cond ((null? body) '())
+	((definition? (car body))
+	 (list	  ; body is a list of expressions, we make one-element list
+	  (cons 'let
+		(cons (make-let-variables (definitions body))
+		      (append (make-setbangs (definitions body))
+			      (non-definitions body))))))
+	(else body)))
+
+(define (definitions body)
+  (cond ((null? body) '())
+	((definition? (car body))
+	 (cons (car body) (definitions (cdr body))))
+	(else '())))
+
+(define (non-definitions body)
+  (cond ((null? body) '())
+	((definition? (car body))
+	 (non-definitions (cdr body)))
+	(else body)))
+
+(define (make-let-variables definitions)
+  (map (lambda (def)
+	 (list (definition-variable def) '(quote *unassigned*)))
+       definitions))
+
+(define (make-setbangs definitions)
+  (map (lambda (def)
+	 (list 'set! (definition-variable def) (definition-value def)))
+       definitions))
+
+
+(c)  It should be in make-procedure, because then the scanning is done only
+once, when the procedure is created, rather than every time the procedure
+is called.  (On the other hand, if Scheme printed procedures in a way that
+showed the body, the user might wonder why the body isn't what s/he wrote.)
+
+(define (make-procedure parameters body env)
+  (list 'procedure parameters (scan-out-defines body) env))
+
+
+4.17
+
+The extra frame is created by the LET we introduced into the procedure body.
+The frame itself would matter only if some expressions were evaluated in the
+outer frame rather than the inner one.  But there are no such expressions,
+except for the (QUOTE *UNASSIGNED*) ones we put in the LET, and those don't
+depend on the environment for their values.
+
+We could do it without the extra frame by scanning
+
+(lambda (args...)
+  (define u e1)
+  (define v e2)
+  ...)
+
+into
+
+(lambda (args)
+  (define u '*unassigned*)
+  (define v '*unassigned*)
+  (set! u e1)
+  (set! v e2)
+  ...)
+
+and continuing to use the sequential version of internal DEFINE already in the
+metacircular evaluator.  (This may seem to have no benefit at all, but it does,
+because the local variables U and V are bound before the expressions E1 and E2
+are evaluated, so we can be sure they won't refer to global variables.)
+
+
+4.18 
+
+You can't actually experiment with this question unless you define DELAY
+and CONS-STREAM as special forms in the metacircular evaluator.
+
+The SOLVE procedure would work using the scan-out approach of 4.16, but not
+using the version proposed in this exercise.  The body of SOLVE would be
+
+	(let ((y '*unassigned*) (dy '*unassigned*))
+	  (let ((gensym1 (integral (delay dy) y0 dt))
+		(GENSYM2 (STREAM-MAP F Y)))
+	    (set! y gensym1)
+	    (set! dy gensym2)
+	    y)
+
+In the line in capital letters, stream-map is an ordinary procedure, so its
+argument expressions are evaluated before stream-map is called.  One of the
+arguments is Y, whose value at this point is *unassigned*, so an error will
+result.  This is consistent with the definition of LETREC in the Scheme
+standard.  (Internal DEFINE is defined by the standard to be equivalent to
+LETREC.  See page 16 of the standard, in the course reader, section 5.5.2.
+Then see pages 11-12 for the discussion of LETREC, especially the last
+paragraph of that section.)
+
+
+4.19
+
+This is answered in the footnote: the authors support Alyssa.
+
+One possible way to get what Eva wants is to use the approach of exercise
+4.16, but instead of giving an error if one of the SET! expressions fails,
+move it to the end of the line, so you keep trying until every variable has a
+value or until no further progress can be made.  So in this example it'd be
+
+	(let ((b '*unassigned*) (a '*unassigned*))
+	  (set!-ignoring-errors b (+ a x))
+	  (set!-ignoring-errors a 5)
+	  (if (unassigned? b) (set! b (+ a x)))
+	  (if (unassigned? a) (set! a 5))
+	  (+ a b))
+
+using pseudo-special-forms SET!-IGNORING-ERRORS and UNASSIGNED? that aren't
+defined but whose meanings should be obvious.  You'd have to repeat the IF
+expressions as many times as you have variables, to be sure that any
+dependency order would work.
+
+Even so, an expression such as
+
+	(define (f x)
+	  (define a (+ b 3))
+	  (define b (+ a 4))
+	  (+ a b))
+
+won't work no matter how many times you try the assignments.
+
+
+4.20
+
+(a)
+
+(define (letrec? exp)
+  (tagged-list? exp 'letrec))
+
+(define (letrec->let exp)
+  (cons 'let
+	(cons (map (lambda (var) (list var '(quote *unassigned*)))
+		   (let-formals exp))
+	      (append (map (lambda (var val) (list 'set! var val))
+			   (let-formals exp)
+			   (let-actuals exp))
+		      (let-body exp)))))
+
+Then add a cond clause to EVAL:
+
+	((letrec? exp) (eval (letrec->let exp) env))
+
+
+(b) In the correct version, after transforming the LETREC as on page 389,
+we have
+
+(define (f x)
+  (let ((even? '*unassigned*) (odd? '*unassigned*))
+    (set! even? (lambda (n) (if (= n 0) true (odd? (- n 1)))))
+    (set! odd? (lambda (n) (if (= n 0) false (even? (- n 1)))))
+    <rest of body of F>))
+
+Evaluating that gives
+
+    global env:  F -> procedure P1
+
+    procedure P1: params (x), body (let ...), global env
+
+When evaluating (F 5), we add
+
+    E1: X -> 5, extends G
+
+The LET expands to a LAMBDA and an invocation:
+
+    procedure P2: params (even? odd?),  body (set! ...)...,  env E1
+
+    E2: EVEN? -> *unassigned*,  ODD? -> *unassigned*,  extends E1
+
+With E2 as the current environment we evaluate the two SET! expressions,
+which create procedures (because of the LAMBDA expressions inside them) and
+change the bindings in E2:
+
+    procedure P3: params (n),  body (if (= n 0) true (odd? ...)),  env E2
+    procedure P4: params (n),  body (if (= n 0) false (even? ...)),  env E2
+
+    E2: EVEN? -> procedure P3,  ODD? -> procedure P4,  extends E1
+
+Note that P3 and P4 are created in E2, so they have access to the bindings
+for EVEN? and ODD?.
+
+Then we evaluate the remaining expression in the body of F, which can use
+EVEN? and ODD? successfully.
+
+By contrast, Louis wants us to evaluate
+
+(define (f x)
+  (let ((even?
+	 (lambda (n)
+	   (if (= n 0)
+	       true
+	       (odd? (- n 1)))))
+	(odd?
+	 (lambda (n)
+	   (if (= n 0)
+	       false
+	       (even? (- n 1))))))
+    <rest of body of F>))
+
+This time, when evaluating (F 5), we still add
+
+    E1: X -> 5, extends G
+
+The LET expands to a LAMBDA and an invocation with procedures as arguments:
+
+    ((lambda (even? odd?) <rest of body>)
+     (lambda (n) (if (= n 0) true (odd? (- n 1))))
+     (lambda (n) (if (= n 0) false (even? (- n 1)))))
+
+The three LAMBDA expressions give us
+
+    procedure P2: params (even? odd?),  body <rest of body>,  env E1
+    procedure P3: params (n),  body (if (= n 0) true (odd? ...)),  env E1
+    procedure P4: params (n),  body (if (= n 0) false (even? ...)),  env E1
+
+We can then invoke P2 with P3 and P4 as its arguments:
+
+    E2: EVEN? -> procedure P3,  ODD? -> procedure P4,  extends E1
+
+In this environment we evaluate <rest of body>.  Suppose it's a simple
+expression: (EVEN? X).  First we evaluate the subexpressions.  In E2 we
+find the binding EVEN? -> P3.  There's no binding for X in frame E2, but
+it extends E1, where we find X -> 5.  Now we invoke P3 by making a new
+environment:
+
+    E3: N -> 5, extends E1
+
+Note that E3 extends E1, not E2, because E1 is where P3 was created.
+
+With E3 as the current environment we evaluate the body of P3, which is
+
+    (if (= n 0) true (odd? (- n 1)))
+
+We easily evaluate (= N 0) and get the answer #F.  We then try to evaluate
+
+    (odd? (- n 1))
+
+But there is no binding for ODD? in E3, including the frames it extends.
+That's why LET instead of LETREC isn't sufficient.
+
+
+4.21
+
+We've actually seen this idea before, in the Extra for Experts in week 2.
+
+(a) FIB without DEFINE/LETREC
+
+((lambda (n)
+   ((lambda (fib) (fib fib n))
+    (lambda (fb k)
+      (if (< k 2)
+	  k
+	  (+ (fb fb (- k 1))
+	     (fb fb (- k 2)))))))
+ 10)
+
+
+(b) EVEN?/ODD? ditto
+
+(define (f x)
+  ((lambda (even? odd?)
+     (even? even? odd? x))
+   (lambda (ev? od? n)		; This is EVEN?
+     (if (= n 0) true (OD? EV? OD? (- N 1))))
+   (lambda (ev? od? n)		; This is ODD?
+     (if (= n 0) false (EV? EV? OD? (- N 1))))))