6.001/Recitation 22
Rob Speer
< 6.001
[edit]
More meta-circular evaluation
Here's our eval and apply again, this time with more abstraction:
(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))))
(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))))
- What changes do we need to make to make this evaluator use dynamic scope instead of lexical?
- Let's replace eval with something data-directed, and then make it possible to add macros.
[edit]
Static analysis
We want to do as much work as possible before we run the program. By just scanning over the code, we know everything but the environment it's running in. So give it everything but the environment, and fill the environment in later.
The new eval:
(define (eval exp env) ((analyze exp) env))
Well, that was trivial and uninformative. The magic is now done by analyze:
(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))))
So we need to implement these parts of analyze.
-
analyze-self-evaluatingis stupidly easy. -
analyze-quotedis easy too. -
analyze-variableat least needs to make use of the environement. - Let's skip
analyze-assignment. -
analyze-definitioncan do almost all of what it needs to do. -
analyze-ifneeds to store a fair amount of state. -
analyze-lambdais where we'll gain a lot of efficiency. Why? -
analyze-sequenceis unexpectedly hard to do, so we'll skip it. -
analyze-applicationis where all the work ofapplyneeds to be done. What do we do once everything's analyzed?
The eval/apply loop is gone. What took its place? What makes recursion work now?
We can work out what happens on this example:
(define (factorial n)
(if (= n 1)
1
(* (factorial (- n 1)) n)))
[edit]
analyze-sequence
Here's Hal and Gerry's code for analyze-sequence:
(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))))
You may have thought it would be simpler, like the following procedure. What's wrong with it?
(define (analyze-sequence exps)
(define (execute-sequence procs env)
(cond ((null? (cdr procs)) ((car procs) env))
(else ((car procs) env)
(execute-sequence (cdr procs) env))))
(let ((procs (map analyze exps)))
(if (null? procs)
(error "Empty sequence -- ANALYZE"))
(lambda (env) (execute-sequence procs env))))
