mirror of
https://github.com/correl/sicp.git
synced 2024-11-27 11:09:58 +00:00
1090 lines
36 KiB
Org Mode
1090 lines
36 KiB
Org Mode
#+TITLE: 4.1 - The Metacircular Evaluator
|
||
#+STARTUP: indent
|
||
#+OPTIONS: num:nil
|
||
|
||
We will be writing a Lisp evaluator implemented in Lisp (hence,
|
||
/metacircular/: an evaluator written in the same language it evaluates).
|
||
|
||
[[file:4.1-lisp.jpg]]
|
||
|
||
[[file:4.1-eval-apply.svg]]
|
||
|
||
The evaluator will be specified using procedures defining the syntax
|
||
of the expressions to be evaluated. Data abstraction will be used to
|
||
make the evaluator independent of the representation of the language.
|
||
|
||
* <<4.1.1>> The Core of the Evaluator
|
||
** Eval
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (eval exp env)
|
||
(cond ((self-evaluating? exp)
|
||
exp)
|
||
((variable? exp)
|
||
(lookup-variable-value exp env))
|
||
((quoted? exp)
|
||
(text-of-quotation exp))
|
||
((assignment? exp)
|
||
(eval-assignment exp env))
|
||
((definition? exp)
|
||
(eval-definition exp env))
|
||
((if? exp)
|
||
(eval-if exp env))
|
||
((lambda? exp)
|
||
(make-procedure
|
||
(lambda-parameters exp)
|
||
(lambda-body exp)
|
||
env))
|
||
((begin? exp)
|
||
(eval-sequence
|
||
(begin-actions exp)
|
||
env))
|
||
((cond? exp)
|
||
(eval (cond->if exp) env))
|
||
((application? exp)
|
||
(apply (eval (operator exp) env)
|
||
(list-of-values
|
||
(operands exp)
|
||
env)))
|
||
(else
|
||
(error "Unknown expression
|
||
type: EVAL" exp))))
|
||
#+END_SRC
|
||
** Apply
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define apply-in-underlying-scheme apply)
|
||
(define (apply procedure arguments)
|
||
(cond ((primitive-procedure? procedure)
|
||
(apply-primitive-procedure
|
||
procedure
|
||
arguments))
|
||
((compound-procedure? procedure)
|
||
(eval-sequence
|
||
(procedure-body procedure)
|
||
(extend-environment
|
||
(procedure-parameters
|
||
procedure)
|
||
arguments
|
||
(procedure-environment
|
||
procedure))))
|
||
(else
|
||
(error "Unknown procedure
|
||
type: APPLY"
|
||
procedure))))
|
||
#+END_SRC
|
||
** Procedure Arguments
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (list-of-values exps env)
|
||
(if (no-operands? exps)
|
||
'()
|
||
(cons (eval (first-operand exps) env)
|
||
(list-of-values
|
||
(rest-operands exps)
|
||
env))))
|
||
#+END_SRC
|
||
** Conditionals
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (eval-if exp env)
|
||
(if (true? (eval (if-predicate exp) env))
|
||
(eval (if-consequent exp) env)
|
||
(eval (if-alternative exp) env)))
|
||
#+END_SRC
|
||
#+BEGIN_QUOTE
|
||
In this case, the language being implemented and the implementation
|
||
language are the same. Contemplation of the meaning of ~true?~ here
|
||
yields expansion of consciousness without the abuse of substance.
|
||
#+END_QUOTE
|
||
** Sequences
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (eval-sequence exps env)
|
||
(cond ((last-exp? exps)
|
||
(eval (first-exp exps) env))
|
||
(else
|
||
(eval (first-exp exps) env)
|
||
(eval-sequence (rest-exps exps)
|
||
env))))
|
||
#+END_SRC
|
||
** Assignments and definitions
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (eval-assignment exp env)
|
||
(set-variable-value!
|
||
(assignment-variable exp)
|
||
(eval (assignment-value exp) env)
|
||
env)
|
||
'ok)
|
||
|
||
(define (eval-definition exp env)
|
||
(define-variable!
|
||
(definition-variable exp)
|
||
(eval (definition-value exp) env)
|
||
env)
|
||
'ok)
|
||
#+END_SRC
|
||
** Exercise 4.1
|
||
Notice that we cannot tell whether the metacircular evaluator
|
||
evaluates operands from left to right or from right to left. Its
|
||
evaluation order is inherited from the underlying Lisp: If the
|
||
arguments to cons in ~list-of-values~ are evaluated from left to
|
||
right, then ~list-of-values~ will evaluate operands from left to
|
||
right; and if the arguments to cons are evaluated from right to left,
|
||
then ~list-of-values~ will evaluate operands from right to left.
|
||
|
||
Write a version of ~list-of-values~ that evaluates operands from left
|
||
to right regardless of the order of evaluation in the underlying
|
||
Lisp. Also write a version of ~list-of-values~ that evaluates operands
|
||
from right to left.
|
||
|
||
----------------------------------------------------------------------
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (list-of-values-ltr exps env)
|
||
(if (no-operands? exps)
|
||
'()
|
||
(let ((first (eval (first-operand exps) env))
|
||
(rest (list-of-values-ltr
|
||
(rest-operands exps)
|
||
env)))
|
||
(cons first rest))))
|
||
(define (list-of-values-rtl exps env)
|
||
(if (no-operands? exps)
|
||
'()
|
||
(let ((rest (list-of-values-rtl
|
||
(rest-operands exps)
|
||
env))
|
||
(first (eval (first-operand exps) env)))
|
||
(cons first rest))))
|
||
#+END_SRC
|
||
* <<4.1.2>> Representing Expressions
|
||
- The only self-evaluating items are numbers and strings:
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (self-evaluating? exp)
|
||
(cond ((number? exp) true)
|
||
((string? exp) true)
|
||
(else false)))
|
||
#+END_SRC
|
||
- Variables are represented by symbols:
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (variable? exp) (symbol? exp))
|
||
#+END_SRC
|
||
- Quotations have the form ~(quote 〈text-of-quotation〉)~:
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (quoted? exp)
|
||
(tagged-list? exp 'quote))
|
||
|
||
(define (text-of-quotation exp)
|
||
(cadr exp))
|
||
#+END_SRC
|
||
|
||
~Quoted?~ is defined in terms of the procedure ~tagged-list?~, which
|
||
identifies lists beginning with a designated symbol:
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (tagged-list? exp tag)
|
||
(if (pair? exp)
|
||
(eq? (car exp) tag)
|
||
false))
|
||
#+END_SRC
|
||
- Assignments have the form ~(set! 〈var〉 〈value〉)~:
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (assignment? exp)
|
||
(tagged-list? exp 'set!))
|
||
|
||
(define (assignment-variable exp)
|
||
(cadr exp))
|
||
|
||
(define (assignment-value exp) (caddr exp))
|
||
#+END_SRC
|
||
- Definitions have the form
|
||
#+BEGIN_EXAMPLE
|
||
(define ⟨var⟩ ⟨value⟩)
|
||
#+END_EXAMPLE
|
||
or the form
|
||
#+BEGIN_EXAMPLE
|
||
(define (⟨var⟩ ⟨param₁⟩ … ⟨paramₙ⟩)
|
||
⟨body⟩)
|
||
#+END_EXAMPLE
|
||
|
||
The latter form (standard procedure definition) is syntactic sugar for
|
||
#+BEGIN_EXAMPLE
|
||
(define ⟨var⟩
|
||
(lambda (⟨param₁⟩ … ⟨paramₙ⟩)
|
||
⟨body⟩))
|
||
#+END_EXAMPLE
|
||
|
||
The corresponding syntax procedures are the following:
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (definition? exp)
|
||
(tagged-list? exp 'define))
|
||
|
||
(define (definition-variable exp)
|
||
(if (symbol? (cadr exp))
|
||
(cadr exp)
|
||
(caadr exp)))
|
||
|
||
(define (definition-value exp)
|
||
(if (symbol? (cadr exp))
|
||
(caddr exp)
|
||
(make-lambda
|
||
(cdadr exp) ; formal parameters
|
||
(cddr exp)))) ; body
|
||
#+END_SRC
|
||
- Lambda expressions are lists that begin with the symbol ~lambda~:
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (lambda? exp)
|
||
(tagged-list? exp 'lambda))
|
||
(define (lambda-parameters exp) (cadr exp))
|
||
(define (lambda-body exp) (cddr exp))
|
||
#+END_SRC
|
||
|
||
We also provide a constructor for lambda expressions, which is used
|
||
by ~definition-value~, above:
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (make-lambda parameters body)
|
||
(cons 'lambda (cons parameters body)))
|
||
#+END_SRC
|
||
- Conditionals begin with ~if~ and have a predicate, a consequent, and
|
||
an (optional) alternative. If the expression has no alternative
|
||
part, we provide ~false~ as the alternative.
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (if? exp) (tagged-list? exp 'if))
|
||
(define (if-predicate exp) (cadr exp))
|
||
(define (if-consequent exp) (caddr exp))
|
||
(define (if-alternative exp)
|
||
(if (not (null? (cdddr exp)))
|
||
(cadddr exp)
|
||
'false))
|
||
#+END_SRC
|
||
|
||
We also provide a constructor for ~if~ expressions, to be used by
|
||
~cond->if~ to transform ~cond~ expressions into ~if~ expressions:
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (make-if predicate
|
||
consequent
|
||
alternative)
|
||
(list 'if
|
||
predicate
|
||
consequent
|
||
alternative))
|
||
#+END_SRC
|
||
- ~Begin~ packages a sequence of expressions into a single
|
||
expression. We include syntax operations on ~begin~ expressions to
|
||
extract the actual sequence from the ~begin~ expression, as well as
|
||
selectors that return the first expression and the rest of the
|
||
expressions in the sequence.
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (begin? exp)
|
||
(tagged-list? exp 'begin))
|
||
(define (begin-actions exp) (cdr exp))
|
||
(define (last-exp? seq) (null? (cdr seq)))
|
||
(define (first-exp seq) (car seq))
|
||
(define (rest-exps seq) (cdr seq))
|
||
#+END_SRC
|
||
|
||
We also include a constructor ~sequence->exp~ (for use by
|
||
~cond->if~) that transforms a sequence into a single expression,
|
||
using ~begin~ if necessary:
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (sequence->exp seq)
|
||
(cond ((null? seq) seq)
|
||
((last-exp? seq) (first-exp seq))
|
||
(else (make-begin seq))))
|
||
|
||
(define (make-begin seq) (cons 'begin seq))
|
||
#+END_SRC
|
||
- A procedure application is any compound expression that is not one
|
||
of the above expression types. The ~car~ of the expression is the
|
||
operator, and the ~cdr~ is the list of operands:
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (application? exp) (pair? exp))
|
||
(define (operator exp) (car exp))
|
||
(define (operands exp) (cdr exp))
|
||
(define (no-operands? ops) (null? ops))
|
||
(define (first-operand ops) (car ops))
|
||
(define (rest-operands ops) (cdr ops))
|
||
#+END_SRC
|
||
** Derived expressions
|
||
~Cond~ can be represented as a nest of if expressions.
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (cond? exp)
|
||
(tagged-list? exp 'cond))
|
||
(define (cond-clauses exp) (cdr exp))
|
||
(define (cond-else-clause? clause)
|
||
(eq? (cond-predicate clause) 'else))
|
||
(define (cond-predicate clause)
|
||
(car clause))
|
||
(define (cond-actions clause)
|
||
(cdr clause))
|
||
(define (cond->if exp)
|
||
(expand-clauses (cond-clauses exp)))
|
||
(define (expand-clauses clauses)
|
||
(if (null? clauses)
|
||
'false ; no else clause
|
||
(let ((first (car clauses))
|
||
(rest (cdr clauses)))
|
||
(if (cond-else-clause? first)
|
||
(if (null? rest)
|
||
(sequence->exp
|
||
(cond-actions first))
|
||
(error "ELSE clause isn't
|
||
last: COND->IF"
|
||
clauses))
|
||
(make-if (cond-predicate first)
|
||
(sequence->exp
|
||
(cond-actions first))
|
||
(expand-clauses
|
||
rest))))))
|
||
#+END_SRC
|
||
|
||
Expressions (such as ~cond~) that we choose to implement as syntactic
|
||
transformations are called derived expressions. ~Let~ expressions are
|
||
also derived expressions (see Exercise 4.6).
|
||
|
||
#+BEGIN_QUOTE
|
||
Practical Lisp systems provide a mechanism that allows a user to add
|
||
new derived expressions and specify their implementation as syntactic
|
||
transformations without modifying the evaluator. Such a user-defined
|
||
transformation is called a macro. Although it is easy to add an
|
||
elementary mechanism for defining macros, the resulting language has
|
||
subtle name-conflict problems. There has been much research on
|
||
mechanisms for macro definition that do not cause these
|
||
difficulties. See, for example, Kohlbecker 1986, Clinger and Rees
|
||
1991, and Hanson 1991.
|
||
#+END_QUOTE
|
||
** Exercise 4.2
|
||
Louis Reasoner plans to reorder the ~cond~ clauses in ~eval~ so that
|
||
the clause for procedure applications appears before the clause for
|
||
assignments. He argues that this will make the interpreter more
|
||
efficient: Since programs usually contain more applications than
|
||
assignments, definitions, and so on, his modified ~eval~ will usually
|
||
check fewer clauses than the original ~eval~ before identifying the
|
||
type of an expression.
|
||
|
||
1. What is wrong with Louis’s plan? (Hint: What will Louis’s evaluator
|
||
do with the expression ~(define x 3)~?)
|
||
|
||
-------------------------------------------------------------------
|
||
|
||
The procedure application check requires only that the expression
|
||
be a pair, which any list will satisfy. For example, the expression
|
||
~(define x 3)~ would trigger application instead of assignment, and
|
||
end up failing.
|
||
2. Louis is upset that his plan didn’t work. He is willing to go to
|
||
any lengths to make his evaluator recognize procedure applications
|
||
before it checks for most other kinds of expressions. Help him by
|
||
changing the syntax of the evaluated language so that procedure
|
||
applications start with call. For example, instead of (factorial 3)
|
||
we will now have to write (call factorial 3) and instead of (+ 1 2)
|
||
we will have to write (call + 1 2).
|
||
|
||
-------------------------------------------------------------------
|
||
|
||
#+BEGIN_SRC scheme
|
||
(define (application? exp)
|
||
(tagged-list exp 'call))
|
||
|
||
(define (operator exp) (cadr exp))
|
||
(define (operands exp) (cddr exp))
|
||
#+END_SRC
|
||
** Exercise 4.3
|
||
Rewrite eval so that the dispatch is done in data-directed
|
||
style. Compare this with the data-directed differentiation procedure
|
||
of Exercise 2.73. (You may use the =car= of a compound expression as
|
||
the type of the expression, as is appropriate for the syntax
|
||
implemented in this section.)
|
||
|
||
----------------------------------------------------------------------
|
||
|
||
Borrowed from [[http://wqzhang.wordpress.com/2009/09/17/sicp-exercise-4-3/][Weiqun Zhang's blog]], for working implementations of
|
||
~get~ and ~put~.
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
;; -------------------------------------------------------------------
|
||
;; Exercise 4.3
|
||
;; -------------------------------------------------------------------
|
||
|
||
(define *op-table* (make-eq-hash-table))
|
||
(define (get op type)
|
||
(hash-table/get *op-table* (list op type) #f))
|
||
(define (put op type proc)
|
||
(hash-table/put! *op-table* (list op type) proc))
|
||
|
||
(define (eval exp env)
|
||
(cond ((self-evaluating? exp) exp)
|
||
((variable? exp) (lookup-variable-value exp env))
|
||
((get (car exp) 'eval)
|
||
((get (car exp) 'eval) exp env))
|
||
((application? exp)
|
||
(apply (eval (operator exp) env)
|
||
(list-of-values (operands exp) env)))
|
||
(else
|
||
(error "Unknown expression type -- EVAL" exp))))
|
||
|
||
(put 'quote 'eval
|
||
(lambda (exp env)
|
||
(text-of-quotation exp)))
|
||
(put 'set! 'eval
|
||
(lambda (exp env)
|
||
(eval-assignment exp env)))
|
||
(put 'define 'eval eval-definition)
|
||
(put 'if 'eval eval-if)
|
||
(put 'lambda 'eval
|
||
(lambda (exp env)
|
||
(make-procedure (lambda-parameters exp)
|
||
(lambda-body exp)
|
||
env)))
|
||
(put 'begin 'eval
|
||
(lambda (exp env)
|
||
(eval-sequence (begin-actions exp) env)))
|
||
(put 'cond 'eval
|
||
(lambda (exp env)
|
||
(eval (cond->if exp) env)))
|
||
#+END_SRC
|
||
** Exercise 4.4
|
||
Recall the definitions of the special forms and and or from Chapter 1:
|
||
|
||
- ~and~: The expressions are evaluated from left to right. If any
|
||
expression evaluates to ~false~, ~false~ is returned; any remaining
|
||
expressions are not evaluated. If all the expressions evaluate to
|
||
true values, the value of the last expression is returned. If there
|
||
are no expressions then ~true~ is returned.
|
||
- ~or~: The expressions are evaluated from left to right. If any
|
||
expression evaluates to a true value, that value is returned; any
|
||
remaining expressions are not evaluated. If all expressions evaluate
|
||
to ~false~, or if there are no expressions, then ~false~ is returned.
|
||
|
||
Install ~and~ and ~or~ as new special forms for the evaluator by
|
||
defining appropriate syntax procedures and evaluation procedures
|
||
~eval-and~ and ~eval-or~. Alternatively, show how to implement ~and~
|
||
and ~or~ as derived expressions.
|
||
|
||
----------------------------------------------------------------------
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
;; -------------------------------------------------------------------
|
||
;; Exercise 4.4
|
||
;; -------------------------------------------------------------------
|
||
|
||
;; Special forms
|
||
|
||
(define (eval-and exp env)
|
||
(define (eval-and-operands exps)
|
||
(if (no-operands? exps)
|
||
true
|
||
(let ((first (eval (first-operand exps) env))
|
||
(rest (rest-operands exps)))
|
||
(if (false? first)
|
||
false
|
||
(if (no-operands? rest)
|
||
first
|
||
(eval-and-operands rest))))))
|
||
(eval-and-operands (operands exp)))
|
||
|
||
(define (eval-or exp env)
|
||
(define (eval-or-operands exps)
|
||
(if (no-operands? exps)
|
||
false
|
||
(let ((first (eval (first-operand exps) env))
|
||
(rest (rest-operands exps)))
|
||
(if (true? first)
|
||
first
|
||
(eval-or-operands rest)))))
|
||
(eval-or-operands (operands exp)))
|
||
|
||
(put 'and 'eval eval-and)
|
||
(put 'or 'eval eval-or)
|
||
|
||
;; Derived expressions
|
||
|
||
(define (and->if exp)
|
||
(define (expand exps)
|
||
(if (null? exps)
|
||
'true
|
||
(make-if (list 'false? (car exps))
|
||
'false
|
||
(if (null? (cdr exps))
|
||
(car exps)
|
||
(expand (cdr exps))))))
|
||
(expand (cdr exp)))
|
||
|
||
(define (or->if exp)
|
||
(define (expand exps)
|
||
(if (null? exps)
|
||
'false
|
||
(make-if (list 'true? (car exps))
|
||
(car exps)
|
||
(expand (cdr exps)))))
|
||
(expand (cdr exp)))
|
||
#+END_SRC
|
||
** Exercise 4.5
|
||
Scheme allows an additional syntax for ~cond~ clauses, ~(⟨test⟩ =>
|
||
⟨recipient⟩)~. If ~⟨test⟩~ evaluates to a true value, then
|
||
~⟨recipient⟩~ is evaluated. Its value must be a procedure of one
|
||
argument; this procedure is then invoked on the value of the ~⟨test⟩~,
|
||
and the result is returned as the value of the ~cond~ expression. For
|
||
example
|
||
|
||
#+BEGIN_SRC scheme
|
||
(cond ((assoc 'b '((a 1) (b 2))) => cadr)
|
||
(else false))
|
||
#+END_SRC
|
||
|
||
returns ~2~. Modify the handling of ~cond~ so that it supports this
|
||
extended syntax.
|
||
|
||
----------------------------------------------------------------------
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
;; -------------------------------------------------------------------
|
||
;; Exercise 4.5
|
||
;; -------------------------------------------------------------------
|
||
|
||
(define (cond-actions clause)
|
||
(if (tagged-list? (cdr clause) '=>)
|
||
(list (list (caddr clause) (cond-predicate clause)))
|
||
(cdr clause)))
|
||
#+END_SRC
|
||
** Exercise 4.6
|
||
Let expressions are derived expressions, because
|
||
|
||
#+BEGIN_SRC scheme
|
||
(let ((⟨var₁⟩ ⟨exp₁⟩) … (⟨varₙ⟩ ⟨expₙ⟩))
|
||
⟨body⟩)
|
||
#+END_SRC
|
||
|
||
is equivalent to
|
||
|
||
#+BEGIN_SRC scheme
|
||
((lambda (⟨var₁⟩ … ⟨varₙ⟩)
|
||
⟨body⟩)
|
||
⟨exp₁⟩
|
||
…
|
||
⟨expₙ⟩)
|
||
#+END_SRC
|
||
|
||
Implement a syntactic transformation ~let->combination~ that reduces
|
||
evaluating ~let~ expressions to evaluating combinations of the type
|
||
shown above, and add the appropriate clause to ~eval~ to handle ~let~
|
||
expressions.
|
||
|
||
----------------------------------------------------------------------
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
;; -------------------------------------------------------------------
|
||
;; Exercise 4.6
|
||
;; -------------------------------------------------------------------
|
||
|
||
(define (let->combination exp)
|
||
(let ((var-alist (cadr exp))
|
||
(body (cddr exp)))
|
||
(append (list (append (list 'lambda
|
||
(map car var-alist))
|
||
body))
|
||
(map cadr var-alist))))
|
||
|
||
|
||
(put 'let 'eval
|
||
(lambda (exp env)
|
||
(eval (let->combination exp) env)))
|
||
#+END_SRC
|
||
** Exercise 4.7
|
||
~Let*~ is similar to ~let~, except that the bindings of the ~let*~
|
||
variables are performed sequentially from left to right, and each
|
||
binding is made in an environment in which all of the preceding
|
||
bindings are visible. For example
|
||
|
||
#+BEGIN_SRC scheme
|
||
(let* ((x 3)
|
||
(y (+ x 2))
|
||
(z (+ x y 5)))
|
||
(* x z))
|
||
#+END_SRC
|
||
|
||
returns ~39~. Explain how a ~let*~ expression can be rewritten as a
|
||
set of nested ~let~ expressions, and write a procedure
|
||
~let*->nested-lets~ that performs this transformation. If we have
|
||
already implemented ~let~ ([[Exercise 4.6]]) and we want to extend the
|
||
evaluator to handle ~let*~, is it sufficient to add a clause to ~eval~
|
||
whose action is
|
||
|
||
#+BEGIN_SRC scheme
|
||
(eval (let*->nested-lets exp) env)
|
||
#+END_SRC
|
||
|
||
or must we explicitly expand ~let*~ in terms of non-derived
|
||
expressions?
|
||
|
||
----------------------------------------------------------------------
|
||
|
||
~Let*~ can be written as a set of nested ~let~ expressions like so:
|
||
|
||
#+BEGIN_SRC scheme
|
||
(let ((x 3))
|
||
(let ((y (+ x 2)))
|
||
(let ((z (+ x y 5)))
|
||
(* x z))))
|
||
#+END_SRC
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
;; -------------------------------------------------------------------
|
||
;; Exercise 5.7
|
||
;; -------------------------------------------------------------------
|
||
|
||
(define (let*->nested-lets exp)
|
||
(define (nested-let var-alist body)
|
||
(if (null? var-alist)
|
||
body
|
||
(let ((var-pair (car var-alist))
|
||
(var-rest (cdr var-alist)))
|
||
(append (list 'let (list var-pair)
|
||
(nested-let var-rest body))))))
|
||
(nested-let (cadr exp)
|
||
(cddr exp)))
|
||
|
||
(put 'let* 'eval
|
||
(lambda (exp env)
|
||
(eval (let*->nested-lets exp) env)))
|
||
#+END_SRC
|
||
|
||
Adding the new clause to ~eval~ is sufficient; it will recursively
|
||
evaluate the resulting expressions, translating ~let*~ to ~let~ to
|
||
combinations along the way.
|
||
** Exercise 4.8
|
||
“Named ~let~” is a variant of ~let~ that has the form
|
||
|
||
#+BEGIN_SRC scheme
|
||
(let ⟨var⟩ ⟨bindings⟩ ⟨body⟩)
|
||
#+END_SRC
|
||
|
||
The ~⟨bindings⟩~ and ~⟨body⟩~ are just as in ordinary ~let~, except
|
||
that ~⟨var⟩~ is bound within ~⟨body⟩~ to a procedure whose body is
|
||
~⟨body⟩~ and whose parameters are the variables in the
|
||
~⟨bindings⟩~. Thus, one can repeatedly execute the ~⟨body⟩~ by
|
||
invoking the procedure named ~⟨var⟩~. For example, the iterative
|
||
Fibonacci procedure (1.2.2) can be rewritten using named ~let~ as
|
||
follows:
|
||
|
||
#+BEGIN_SRC scheme
|
||
(define (fib n)
|
||
(let fib-iter ((a 1) (b 0) (count n))
|
||
(if (= count 0)
|
||
b
|
||
(fib-iter (+ a b)
|
||
a
|
||
(- count 1)))))
|
||
#+END_SRC
|
||
|
||
Modify ~let->combination~ of [[Exercise 4.6]] to also support named ~let~.
|
||
|
||
----------------------------------------------------------------------
|
||
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
;; -------------------------------------------------------------------
|
||
;; Exercise 4.7
|
||
;; -------------------------------------------------------------------
|
||
|
||
(define (let->combination exp)
|
||
(define (let-combination var-alist body)
|
||
(append (list (append (list 'lambda
|
||
(map car var-alist))
|
||
body))
|
||
(map cadr var-alist)))
|
||
(define (named-let-combination name var-alist body)
|
||
(let-combination var-alist
|
||
(append (list (append (list 'define
|
||
(cons name (map car var-alist)))
|
||
body))
|
||
(list (cons name (map car var-alist))))))
|
||
(cond ((alist? (cadr exp))
|
||
(let-combination (cadr exp) (cddr exp)))
|
||
((symbol? (cadr exp))
|
||
(named-let-combination (cadr exp)
|
||
(caddr exp)
|
||
(cdddr exp)))
|
||
(else (error "Invalid expression -- LET"))))
|
||
|
||
(put 'let 'eval
|
||
(lambda (exp env)
|
||
(eval (let->combination exp) env)))
|
||
#+END_SRC
|
||
** Exercise 4.9
|
||
Many languages support a variety of iteration constructs, such as
|
||
~do~, ~for~, ~while~, and ~until~. In Scheme, iterative processes can
|
||
be expressed in terms of ordinary procedure calls, so special
|
||
iteration constructs provide no essential gain in computational
|
||
power. On the other hand, such constructs are often convenient. Design
|
||
some iteration constructs, give examples of their use, and show how to
|
||
implement them as derived expressions.
|
||
** Exercise 4.10
|
||
By using data abstraction, we were able to write an ~eval~ procedure
|
||
that is independent of the particular syntax of the language to be
|
||
evaluated. To illustrate this, design and implement a new syntax for
|
||
Scheme by modifying the procedures in this section, without changing
|
||
~eval~ or ~apply~.
|
||
* <<4.1.3>> Evaluator Data Structures
|
||
** Testing of predicates
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (true? x)
|
||
(not (eq? x false)))
|
||
|
||
(define (false? x)
|
||
(eq? x false))
|
||
#+END_SRC
|
||
** Representing procedures
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (make-procedure parameters body env)
|
||
(list 'procedure parameters body env))
|
||
(define (compound-procedure? p)
|
||
(tagged-list? p 'procedure))
|
||
(define (procedure-parameters p) (cadr p))
|
||
(define (procedure-body p) (caddr p))
|
||
(define (procedure-environment p) (cadddr p))
|
||
#+END_SRC
|
||
** Operations on Environments
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (enclosing-environment env) (cdr env))
|
||
(define (first-frame env) (car env))
|
||
(define the-empty-environment '())
|
||
#+END_SRC
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (make-frame variables values)
|
||
(cons variables values))
|
||
(define (frame-variables frame) (car frame))
|
||
(define (frame-values frame) (cdr frame))
|
||
(define (add-binding-to-frame! var val frame)
|
||
(set-car! frame (cons var (car frame)))
|
||
(set-cdr! frame (cons val (cdr frame))))
|
||
#+END_SRC
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (extend-environment vars vals base-env)
|
||
(if (= (length vars) (length vals))
|
||
(cons (make-frame vars vals) base-env)
|
||
(if (< (length vars) (length vals))
|
||
(error "Too many arguments supplied"
|
||
vars
|
||
vals)
|
||
(error "Too few arguments supplied"
|
||
vars
|
||
vals))))
|
||
#+END_SRC
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(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))
|
||
(car vals))
|
||
(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))
|
||
#+END_SRC
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (set-variable-value! var val env)
|
||
(define (env-loop env)
|
||
(define (scan vars vals)
|
||
(cond ((null? vars)
|
||
(env-loop
|
||
(enclosing-environment env)))
|
||
((eq? var (car vars))
|
||
(set-car! vals val))
|
||
(else (scan (cdr vars)
|
||
(cdr vals)))))
|
||
(if (eq? env the-empty-environment)
|
||
(error "Unbound variable: SET!" var)
|
||
(let ((frame (first-frame env)))
|
||
(scan (frame-variables frame)
|
||
(frame-values frame)))))
|
||
(env-loop env))
|
||
#+END_SRC
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (define-variable! var val env)
|
||
(let ((frame (first-frame env)))
|
||
(define (scan vars vals)
|
||
(cond ((null? vars)
|
||
(add-binding-to-frame!
|
||
var val frame))
|
||
((eq? var (car vars))
|
||
(set-car! vals val))
|
||
(else (scan (cdr vars)
|
||
(cdr vals)))))
|
||
(scan (frame-variables frame)
|
||
(frame-values frame))))
|
||
#+END_SRC
|
||
* <<4.1.4>> Running the Evaluator as a Program
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (primitive-procedure? proc)
|
||
(tagged-list? proc 'primitive))
|
||
|
||
(define (primitive-implementation proc)
|
||
(cadr proc))
|
||
#+END_SRC
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define primitive-procedures
|
||
(list (list 'car car)
|
||
(list 'cdr cdr)
|
||
(list 'cons cons)
|
||
(list 'null? null?)
|
||
;; ⟨more primitives⟩
|
||
(list '= =)
|
||
(list '+ +)
|
||
(list '- -)
|
||
(list '* *)
|
||
(list '/ /)
|
||
))
|
||
|
||
(define (primitive-procedure-names)
|
||
(map car primitive-procedures))
|
||
|
||
(define (primitive-procedure-objects)
|
||
(map (lambda (proc)
|
||
(list 'primitive (cadr proc)))
|
||
primitive-procedures))
|
||
#+END_SRC
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (setup-environment)
|
||
(let ((initial-env
|
||
(extend-environment
|
||
(primitive-procedure-names)
|
||
(primitive-procedure-objects)
|
||
the-empty-environment)))
|
||
(define-variable! 'true true initial-env)
|
||
(define-variable! 'false false initial-env)
|
||
initial-env))
|
||
|
||
(define the-global-environment
|
||
(setup-environment))
|
||
#+END_SRC
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (apply-primitive-procedure proc args)
|
||
(apply-in-underlying-scheme
|
||
(primitive-implementation proc) args))
|
||
#+END_SRC
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define input-prompt ";;; M-Eval input:")
|
||
(define output-prompt ";;; M-Eval value:")
|
||
|
||
(define (driver-loop)
|
||
(prompt-for-input input-prompt)
|
||
(let ((input (read)))
|
||
(let ((output
|
||
(eval input
|
||
the-global-environment)))
|
||
(announce-output output-prompt)
|
||
(user-print output)))
|
||
(driver-loop))
|
||
|
||
(define (prompt-for-input string)
|
||
(newline) (newline)
|
||
(display string) (newline))
|
||
|
||
(define (announce-output string)
|
||
(newline) (display string) (newline))
|
||
#+END_SRC
|
||
#+BEGIN_SRC scheme :tangle yes
|
||
(define (user-print object)
|
||
(if (compound-procedure? object)
|
||
(display
|
||
(list 'compound-procedure
|
||
(procedure-parameters object)
|
||
(procedure-body object)
|
||
'<procedure-env>))
|
||
(display object)))
|
||
#+END_SRC
|
||
|
||
Now all we need to do to run the evaluator is to initialize the global
|
||
environment and start the driver loop. Here is a sample interaction:
|
||
|
||
#+BEGIN_SRC scheme
|
||
(define the-global-environment
|
||
(setup-environment))
|
||
|
||
(driver-loop)
|
||
|
||
;;; M-Eval input:
|
||
(define (append x y)
|
||
(if (null? x)
|
||
y
|
||
(cons (car x) (append (cdr x) y))))
|
||
|
||
;;; M-Eval value:
|
||
ok
|
||
|
||
;;; M-Eval input:
|
||
(append '(a b c) '(d e f))
|
||
|
||
;;; M-Eval value:
|
||
(a b c d e f)
|
||
#+END_SRC
|
||
* <<4.1.5>> Data as Programs
|
||
our evaluator is seen to be a universal machine. It mimics other
|
||
machines when these are described as Lisp programs. This is
|
||
striking.
|
||
|
||
#+BEGIN_QUOTE
|
||
The fact that the machines are described in Lisp is inessential. If we
|
||
give our evaluator a Lisp program that behaves as an evaluator for
|
||
some other language, say C, the Lisp evaluator will emulate the C
|
||
evaluator, which in turn can emulate any machine described as a C
|
||
program. Similarly, writing a Lisp evaluator in C produces a C program
|
||
that can execute any Lisp program. The deep idea here is that any
|
||
evaluator can emulate any other. Thus, the notion of “what can in
|
||
principle be computed” (ignoring practicalities of time and memory
|
||
required) is independent of the language or the computer, and instead
|
||
reflects an underlying notion of computability. This was first
|
||
demonstrated in a clear way by Alan M. Turing (1912-1954), whose 1936
|
||
paper laid the foundations for theoretical computer science. In the
|
||
paper, Turing presented a simple computational model—now known as a
|
||
Turing machine—and argued that any “effective process” can be
|
||
formulated as a program for such a machine. (This argument is known as
|
||
the Church-Turing thesis.) Turing then implemented a universal
|
||
machine, i.e., a Turing machine that behaves as an evaluator for
|
||
Turing-machine programs. He used this framework to demonstrate that
|
||
there are well-posed problems that cannot be computed by Turing
|
||
machines (see [[Exercise 4.15]]), and so by implication cannot be
|
||
formulated as “effective processes.” Turing went on to make
|
||
fundamental contributions to practical computer science as well. For
|
||
example, he invented the idea of structuring programs using
|
||
general-purpose subroutines. See Hodges 1983 for a biography of
|
||
Turing.
|
||
#+END_QUOTE
|
||
* <<4.1.6>> Internal Definitions
|
||
* <<4.1.7>> Separating Syntatic Analysis from Execution
|
||
By separating syntax analysis from execution in ~eval~, we can ensure
|
||
we don't need to repeat the expensive analysis step for each
|
||
subsequent execution.
|
||
|
||
#+BEGIN_SRC scheme
|
||
(define (eval exp env) ((analyze exp) env))
|
||
#+END_SRC
|
||
|
||
The result of calling ~analyze~ is the execution procedure to be
|
||
applied to the environment. The ~analyze~ procedure is the same case
|
||
analysis as performed by the original ~eval~ of [[4.1.1]], except that the
|
||
procedures to which we dispatch perform only analysis, not full
|
||
evaluation:
|
||
|
||
#+BEGIN_SRC scheme
|
||
(define (analyze exp)
|
||
(cond ((self-evaluating? exp)
|
||
(analyze-self-evaluating exp))
|
||
((quoted? exp)
|
||
(analyze-quoted exp))
|
||
((variable? exp)
|
||
(analyze-variable exp))
|
||
((assignment? exp)
|
||
(analyze-assignment exp))
|
||
((definition? exp)
|
||
(analyze-definition exp))
|
||
((if? exp)
|
||
(analyze-if exp))
|
||
((lambda? exp)
|
||
(analyze-lambda exp))
|
||
((begin? exp)
|
||
(analyze-sequence
|
||
(begin-actions exp)))
|
||
((cond? exp)
|
||
(analyze (cond->if exp)))
|
||
((application? exp)
|
||
(analyze-application exp))
|
||
(else
|
||
(error "Unknown expression
|
||
type: ANALYZE"
|
||
exp))))
|
||
#+END_SRC
|
||
|
||
#+BEGIN_SRC scheme
|
||
(define (analyze-self-evaluating exp)
|
||
(lambda (env) exp))
|
||
|
||
(define (analyze-quoted exp)
|
||
(let ((qval (text-of-quotation exp)))
|
||
(lambda (env) qval)))
|
||
|
||
(define (analyze-variable exp)
|
||
(lambda (env)
|
||
(lookup-variable-value exp env)))
|
||
|
||
(define (analyze-assignment exp)
|
||
(let ((var (assignment-variable exp))
|
||
(vproc (analyze
|
||
(assignment-value exp))))
|
||
(lambda (env)
|
||
(set-variable-value!
|
||
var (vproc env) env)
|
||
'ok)))
|
||
|
||
(define (analyze-definition exp)
|
||
(let ((var (definition-variable exp))
|
||
(vproc (analyze
|
||
(definition-value exp))))
|
||
(lambda (env)
|
||
(define-variable! var (vproc env) env)
|
||
'ok)))
|
||
|
||
(define (analyze-if exp)
|
||
(let ((pproc (analyze (if-predicate exp)))
|
||
(cproc (analyze (if-consequent exp)))
|
||
(aproc (analyze (if-alternative exp))))
|
||
(lambda (env)
|
||
(if (true? (pproc env))
|
||
(cproc env)
|
||
(aproc env)))))
|
||
|
||
(define (analyze-lambda exp)
|
||
(let ((vars (lambda-parameters exp))
|
||
(bproc (analyze-sequence
|
||
(lambda-body exp))))
|
||
(lambda (env)
|
||
(make-procedure vars bproc env))))
|
||
|
||
(define (analyze-sequence exps)
|
||
(define (sequentially proc1 proc2)
|
||
(lambda (env) (proc1 env) (proc2 env)))
|
||
(define (loop first-proc rest-procs)
|
||
(if (null? rest-procs)
|
||
first-proc
|
||
(loop (sequentially first-proc
|
||
(car rest-procs))
|
||
(cdr rest-procs))))
|
||
(let ((procs (map analyze exps)))
|
||
(if (null? procs)
|
||
(error "Empty sequence: ANALYZE"))
|
||
(loop (car procs) (cdr procs))))
|
||
|
||
(define (analyze-application exp)
|
||
(let ((fproc (analyze (operator exp)))
|
||
(aprocs (map analyze (operands exp))))
|
||
(lambda (env)
|
||
(execute-application
|
||
(fproc env)
|
||
(map (lambda (aproc) (aproc env))
|
||
aprocs)))))
|
||
|
||
(define (execute-application proc args)
|
||
(cond ((primitive-procedure? proc)
|
||
(apply-primitive-procedure proc args))
|
||
((compound-procedure? proc)
|
||
((procedure-body proc)
|
||
(extend-environment
|
||
(procedure-parameters proc)
|
||
args
|
||
(procedure-environment proc))))
|
||
(else (error "Unknown procedure type:
|
||
EXECUTE-APPLICATION"
|
||
proc))))
|
||
#+END_SRC
|
||
|
||
Our new evaluator uses the same data structures, syntax procedures,
|
||
and run-time support procedures as in [[4.1.2]], [[4.1.3]], and [[4.1.4]].
|