SICP 5.4 The Explicit-Control Evaluator
2025-05-02 Fri
Previously we have designed and simulated a stack-and-registers-based machine to compute factorials, and one to compute fibonaccis.
Section 5.4 describes a machine for general purpose computing (Cf. p. 566). It does so, by translating the metacircular evaluator — a Scheme program which evaluates Scheme expressions — into the controller of a machine with the appropriate data paths (registers and operations).
The metacircular evaluator showed us the general structure of the
evaluation process in terms of eval
and apply
. The
explicit-control evaluator shows how to translate that in terms of
operations on registers and stacks.
This […] fill[s] in the gap in our understanding of how Scheme expressions are interpreted, by providing an explicit model for the mechanisms of control in the evaluator. (p. 492)
We can now see how ``a subexpression manages to return a value to the expression that uses this value'' (p. 491) and why ``some recursive procedures generate iterative processes (that is, are evaluated using constant space) whereas other recursive procedures generate recursive processes'' (p. 491) — a distinction introduced in Chapter 1.
Studying the explicit-control evaluator — as well the other virtual machines we have designed — is a great exercise in digesting how real machines operate, since the language of the controller is very similar to actual Assembly languages.
Here is all the code:
#lang sicp (define (prompt-for-input string) (newline) (newline) (display string) (newline)) (define (get-global-environment) the-global-environment) (define (announce-output string) (newline) (display string) (newline)) (define (user-print object) (if (compound-procedure? object) (display (list 'compound-procedure (procedure-parameters object) (procedure-body object) '<procedure-env>)) (display object))) (define (variable? exp) (symbol? exp)) (define (quoted? exp) (tagged-list? exp 'quote)) (define (assignment? exp) (tagged-list? exp 'set!)) (define (definition? exp) (tagged-list? exp 'define)) (define (if? exp) (tagged-list? exp 'if)) (define (lambda? exp) (tagged-list? exp 'lambda)) (define (begin? exp) (tagged-list? exp 'begin)) (define (application? exp) (pair? exp)) (define (enclosing-environment env) (cdr env)) (define the-empty-environment '()) (define (first-frame env) (car env)) (define (frame-variables frame) (car frame)) (define (frame-values frame) (cdr frame)) (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)) (define (text-of-quotation exp) (cadr exp)) (define (lambda-parameters exp) (cadr exp)) (define (lambda-body exp) (cddr exp)) (define (make-procedure parameters body env) (list 'procedure parameters body env)) (define (operands exp) (cdr exp)) (define (operator exp) (car exp)) (define (first-operand ops) (car ops)) (define (no-operands? ops) (null? ops)) (define (rest-operands ops) (cdr ops)) (define (primitive-procedure? proc) (tagged-list? proc 'primitive)) (define (compound-procedure? p) (tagged-list? p 'procedure)) (define apply-in-underlying-scheme apply) (define (primitive-implementation proc) (cadr proc)) (define (apply-primitive-procedure proc args) (apply-in-underlying-scheme (primitive-implementation proc) args)) (define (procedure-parameters p) (cadr p)) (define (procedure-environment p) (cadddr p)) (define (make-frame variables values) (cons variables values)) (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)))) (define (procedure-body p) (caddr p)) (define (begin-actions exp) (cdr exp)) (define (first-exp seq) (car seq)) (define (last-exp? seq) (null? (cdr seq))) (define (rest-exps seq) (cdr seq)) (define (if-predicate exp) (cadr exp)) (define (true? x) (not (eq? x false))) (define (if-alternative exp) (if (not (null? (cdddr exp))) (cadddr exp) 'false)) (define (if-consequent exp) (caddr exp)) (define (assignment-variable exp) (cadr exp)) (define (assignment-value exp) (caddr exp)) (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)) (define (definition-variable exp) (if (symbol? (cadr exp)) (cadr exp) (caadr exp))) (define (make-lambda parameters body) (cons 'lambda (cons parameters body))) (define (definition-value exp) (if (symbol? (cadr exp)) (caddr exp) (make-lambda (cdadr exp) ; formal parameters (cddr exp)))) ; body (define (add-binding-to-frame! var val frame) (set-car! frame (cons var (car frame))) (set-cdr! frame (cons val (cdr frame)))) (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)))) (define (tagged-list? exp tag) (if (pair? exp) (eq? (car exp) tag) false)) (define (make-machine register-names ops controller-text) (let ((machine (make-new-machine))) (for-each (lambda (register-name) ((machine 'allocate-register) register-name)) register-names) ((machine 'install-operations) ops) ((machine 'install-instruction-sequence) (assemble controller-text machine)) machine)) (define (make-register name) (let ((contents '*unassigned*)) (define (dispatch message) (cond ((eq? message 'get) contents) ((eq? message 'set) (lambda (value) (set! contents value))) (else (error "Unknown request -- REGISTER" message)))) dispatch)) (define (get-contents register) (register 'get)) (define (set-contents! register value) ((register 'set) value)) (define (make-stack) (let ((s '())) (define (push x) (set! s (cons x s))) (define (pop) (if (null? s) (error "Empty stack -- POP") (let ((top (car s))) (set! s (cdr s)) top))) (define (initialize) (set! s '()) 'done) (define (dispatch message) (cond ((eq? message 'push) push) ((eq? message 'pop) (pop)) ((eq? message 'initialize) (initialize)) (else (error "Unknown request -- STACK" message)))) dispatch)) (define (pop stack) (stack 'pop)) (define (push stack value) ((stack 'push) value)) (define (make-new-machine) (let ((pc (make-register 'pc)) (flag (make-register 'flag)) (stack (make-stack)) (the-instruction-sequence '())) (let ((the-ops (list (list 'initialize-stack (lambda () (stack 'initialize))))) (register-table (list (list 'pc pc) (list 'flag flag)))) (define (allocate-register name) (if (assoc name register-table) (error "Multiply defined register: " name) (set! register-table (cons (list name (make-register name)) register-table))) 'register-allocated) (define (lookup-register name) (let ((val (assoc name register-table))) (if val (cadr val) (error "Unknown register:" name)))) (define (execute) (let ((insts (get-contents pc))) (if (null? insts) 'done (begin ((instruction-execution-proc (car insts))) (execute))))) (define (dispatch message) (cond ((eq? message 'start) (set-contents! pc the-instruction-sequence) (execute)) ((eq? message 'install-instruction-sequence) (lambda (seq) (set! the-instruction-sequence seq))) ((eq? message 'allocate-register) allocate-register) ((eq? message 'get-register) lookup-register) ((eq? message 'install-operations) (lambda (ops) (set! the-ops (append the-ops ops)))) ((eq? message 'stack) stack) ((eq? message 'operations) the-ops) (else (error "Unknown request -- MACHINE" message)))) dispatch))) (define (start machine) (machine 'start)) (define (get-register-contents machine register-name) (get-contents (get-register machine register-name))) (define (set-register-contents! machine register-name value) (set-contents! (get-register machine register-name) value) 'done) (define (get-register machine reg-name) ((machine 'get-register) reg-name)) (define (assemble controller-text machine) (let ((result (extract-labels controller-text))) (let ((insts (car result)) (labels (cdr result))) (update-insts! insts labels machine) insts))) (define (extract-labels text) (if (null? text) (cons '() '()) (let ((result (extract-labels (cdr text)))) (let ((insts (car result)) (labels (cdr result))) (let ((next-inst (car text))) (if (symbol? next-inst) (cons insts (cons (make-label-entry next-inst insts) labels)) (cons (cons (make-instruction next-inst) insts) labels))))))) (define (update-insts! insts labels machine) (let ((pc (get-register machine 'pc)) (flag (get-register machine 'flag)) (stack (machine 'stack)) (ops (machine 'operations))) (for-each (lambda (inst) (set-instruction-execution-proc! inst (make-execution-procedure (instruction-text inst) labels machine pc flag stack ops))) insts))) (define (make-instruction text) (cons text '())) (define (instruction-text inst) (car inst)) (define (instruction-execution-proc inst) (cdr inst)) (define (set-instruction-execution-proc! inst proc) (set-cdr! inst proc)) (define (make-label-entry label-name insts) (cons label-name insts)) (define (lookup-label labels label-name) (let ((val (assoc label-name labels))) (if val (cdr val) (error "Undefined label -- ASSEMBLE" label-name)))) (define (make-execution-procedure inst labels machine pc flag stack ops) (cond ((eq? (car inst) 'assign) (make-assign inst machine labels ops pc)) ((eq? (car inst) 'test) (make-test inst machine labels ops flag pc)) ((eq? (car inst) 'branch) (make-branch inst machine labels flag pc)) ((eq? (car inst) 'goto) (make-goto inst machine labels pc)) ((eq? (car inst) 'save) (make-save inst machine stack pc)) ((eq? (car inst) 'restore) (make-restore inst machine stack pc)) ((eq? (car inst) 'perform) (make-perform inst machine labels ops pc)) (else (error "Unknown instruction type -- ASSEMBLE" inst)))) (define (make-assign inst machine labels operations pc) (let ((target (get-register machine (assign-reg-name inst))) (value-exp (assign-value-exp inst))) (let ((value-proc (if (operation-exp? value-exp) (make-operation-exp value-exp machine labels operations) (make-primitive-exp (car value-exp) machine labels)))) (lambda () ; execution procedure for `assign' (set-contents! target (value-proc)) (advance-pc pc))))) (define (assign-reg-name assign-instruction) (cadr assign-instruction)) (define (assign-value-exp assign-instruction) (cddr assign-instruction)) (define (advance-pc pc) (set-contents! pc (cdr (get-contents pc)))) (define (make-test inst machine labels operations flag pc) (let ((condition (test-condition inst))) (if (operation-exp? condition) (let ((condition-proc (make-operation-exp condition machine labels operations))) (lambda () (set-contents! flag (condition-proc)) (advance-pc pc))) (error "Bad TEST instruction -- ASSEMBLE" inst)))) (define (test-condition test-instruction) (cdr test-instruction)) (define (make-branch inst machine labels flag pc) (let ((dest (branch-dest inst))) (if (label-exp? dest) (let ((insts (lookup-label labels (label-exp-label dest)))) (lambda () (if (get-contents flag) (set-contents! pc insts) (advance-pc pc)))) (error "Bad BRANCH instruction -- ASSEMBLE" inst)))) (define (branch-dest branch-instruction) (cadr branch-instruction)) (define (make-goto inst machine labels pc) (let ((dest (goto-dest inst))) (cond ((label-exp? dest) (let ((insts (lookup-label labels (label-exp-label dest)))) (lambda () (set-contents! pc insts)))) ((register-exp? dest) (let ((reg (get-register machine (register-exp-reg dest)))) (lambda () (set-contents! pc (get-contents reg))))) (else (error "Bad GOTO instruction -- ASSEMBLE" inst))))) (define (goto-dest goto-instruction) (cadr goto-instruction)) (define (make-save inst machine stack pc) (let ((reg (get-register machine (stack-inst-reg-name inst)))) (lambda () (push stack (get-contents reg)) (advance-pc pc)))) (define (make-restore inst machine stack pc) (let ((reg (get-register machine (stack-inst-reg-name inst)))) (lambda () (set-contents! reg (pop stack)) (advance-pc pc)))) (define (stack-inst-reg-name stack-instruction) (cadr stack-instruction)) (define (make-perform inst machine labels operations pc) (let ((action (perform-action inst))) (if (operation-exp? action) (let ((action-proc (make-operation-exp action machine labels operations))) (lambda () (action-proc) (advance-pc pc))) (error "Bad PERFORM instruction -- ASSEMBLE" inst)))) (define (perform-action inst) (cdr inst)) (define (make-primitive-exp exp machine labels) (cond ((constant-exp? exp) (let ((c (constant-exp-value exp))) (lambda () c))) ((label-exp? exp) (let ((insts (lookup-label labels (label-exp-label exp)))) (lambda () insts))) ((register-exp? exp) (let ((r (get-register machine (register-exp-reg exp)))) (lambda () (get-contents r)))) (else (error "Unknown expression type -- ASSEMBLE" exp)))) (define (register-exp? exp) (tagged-list? exp 'reg)) (define (register-exp-reg exp) (cadr exp)) (define (constant-exp? exp) (tagged-list? exp 'const)) (define (constant-exp-value exp) (cadr exp)) (define (label-exp? exp) (tagged-list? exp 'label)) (define (label-exp-label exp) (cadr exp)) (define (make-operation-exp exp machine labels operations) (let ((op (lookup-prim (operation-exp-op exp) operations)) (aprocs (map (lambda (e) (make-primitive-exp e machine labels)) (operation-exp-operands exp)))) (lambda () (apply op (map (lambda (p) (p)) aprocs))))) (define (operation-exp? exp) (and (pair? exp) (tagged-list? (car exp) 'op))) (define (operation-exp-op operation-exp) (cadr (car operation-exp))) (define (operation-exp-operands operation-exp) (cdr operation-exp)) (define (lookup-prim symbol operations) (let ((val (assoc symbol operations))) (if val (cadr val) (error "Unknown operation -- ASSEMBLE" symbol)))) (define (self-evaluating? exp) (cond ((number? exp) true) ((string? exp) true) (else false))) (define (last-operand? ops) (null? (cdr ops))) (define (empty-arglist) '()) (define (adjoin-arg arg arglist) (append arglist (list arg))) (define eceval-operations (list (list 'self-evaluating? self-evaluating?) (list 'empty-arglist empty-arglist) (list 'prompt-for-input prompt-for-input) (list 'read read) (list 'get-global-environment get-global-environment) (list 'announce-output announce-output) (list 'user-print user-print) (list 'variable? variable?) (list 'quoted? quoted?) (list 'assignment? assignment?) (list 'definition? definition?) (list 'if? if?) (list 'lambda? lambda?) (list 'begin? begin?) (list 'application? application?) (list 'lookup-variable-value lookup-variable-value) (list 'text-of-quotation text-of-quotation) (list 'lambda-parameters lambda-parameters) (list 'lambda-body lambda-body) (list 'make-procedure make-procedure) (list 'operands operands) (list 'operator operator) (list 'first-operand first-operand) (list 'no-operands? no-operands?) (list 'last-operand? last-operand?) (list 'adjoin-arg adjoin-arg) (list 'rest-operands rest-operands) (list 'primitive-procedure? primitive-procedure?) (list 'compound-procedure? compound-procedure?) (list 'apply-primitive-procedure apply-primitive-procedure) (list 'procedure-parameters procedure-parameters) (list 'procedure-environment procedure-environment) (list 'extend-environment extend-environment) (list 'procedure-body procedure-body) (list 'begin-actions begin-actions) (list 'first-exp first-exp) (list 'last-exp? last-exp?) (list 'rest-exps rest-exps) (list 'if-predicate if-predicate) (list 'true? true?) (list 'if-alternative if-alternative) (list 'if-consequent if-consequent) (list 'assignment-variable assignment-variable) (list 'assignment-value assignment-value) (list 'set-variable-value! set-variable-value!) (list 'definition-variable definition-variable) (list 'definition-value definition-value) (list 'define-variable! define-variable!))) (define eceval (make-machine '(exp env val proc argl continue unev) eceval-operations '( read-eval-print-loop (perform (op initialize-stack)) (perform (op prompt-for-input) (const ";;; EC-Eval input:")) (assign exp (op read)) (assign env (op get-global-environment)) (assign continue (label print-result)) (goto (label eval-dispatch)) print-result (perform (op announce-output) (const ";;; EC-Eval value:")) (perform (op user-print) (reg val)) (goto (label read-eval-print-loop)) unknown-expression-type (assign val (const unknown-expression-type-error)) (goto (label signal-error)) unknown-procedure-type (restore continue) ; clean up stack (from `apply-dispatch') (assign val (const unknown-procedure-type-error)) (goto (label signal-error)) signal-error (perform (op user-print) (reg val)) (goto (label read-eval-print-loop)) eval-dispatch (test (op self-evaluating?) (reg exp)) (branch (label ev-self-eval)) (test (op variable?) (reg exp)) (branch (label ev-variable)) (test (op quoted?) (reg exp)) (branch (label ev-quoted)) (test (op assignment?) (reg exp)) (branch (label ev-assignment)) (test (op definition?) (reg exp)) (branch (label ev-definition)) (test (op if?) (reg exp)) (branch (label ev-if)) (test (op lambda?) (reg exp)) (branch (label ev-lambda)) (test (op begin?) (reg exp)) (branch (label ev-begin)) (test (op application?) (reg exp)) (branch (label ev-application)) (goto (label unknown-expression-type)) ev-self-eval (assign val (reg exp)) (goto (reg continue)) ev-variable (assign val (op lookup-variable-value) (reg exp) (reg env)) (goto (reg continue)) ev-quoted (assign val (op text-of-quotation) (reg exp)) (goto (reg continue)) ev-lambda (assign unev (op lambda-parameters) (reg exp)) (assign exp (op lambda-body) (reg exp)) (assign val (op make-procedure) (reg unev) (reg exp) (reg env)) (goto (reg continue)) ev-application (save continue) (save env) (assign unev (op operands) (reg exp)) (save unev) (assign exp (op operator) (reg exp)) (assign continue (label ev-appl-did-operator)) (goto (label eval-dispatch)) ev-appl-did-operator (restore unev) ; the operands (restore env) (assign argl (op empty-arglist)) (assign proc (reg val)) ; the operator (test (op no-operands?) (reg unev)) (branch (label apply-dispatch)) (save proc) ev-appl-operand-loop (save argl) (assign exp (op first-operand) (reg unev)) (test (op last-operand?) (reg unev)) (branch (label ev-appl-last-arg)) (save env) (save unev) (assign continue (label ev-appl-accumulate-arg)) (goto (label eval-dispatch)) ev-appl-accumulate-arg (restore unev) (restore env) (restore argl) (assign argl (op adjoin-arg) (reg val) (reg argl)) (assign unev (op rest-operands) (reg unev)) (goto (label ev-appl-operand-loop)) ev-appl-last-arg (assign continue (label ev-appl-accum-last-arg)) (goto (label eval-dispatch)) ev-appl-accum-last-arg (restore argl) (assign argl (op adjoin-arg) (reg val) (reg argl)) (restore proc) (goto (label apply-dispatch)) apply-dispatch (test (op primitive-procedure?) (reg proc)) (branch (label primitive-apply)) (test (op compound-procedure?) (reg proc)) (branch (label compound-apply)) (goto (label unknown-procedure-type)) primitive-apply (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) (restore continue) (goto (reg continue)) compound-apply (assign unev (op procedure-parameters) (reg proc)) (assign env (op procedure-environment) (reg proc)) (assign env (op extend-environment) (reg unev) (reg argl) (reg env)) (assign unev (op procedure-body) (reg proc)) (goto (label ev-sequence)) ev-begin (assign unev (op begin-actions) (reg exp)) (save continue) (goto (label ev-sequence)) ev-sequence (assign exp (op first-exp) (reg unev)) (test (op last-exp?) (reg unev)) (branch (label ev-sequence-last-exp)) (save unev) (save env) (assign continue (label ev-sequence-continue)) (goto (label eval-dispatch)) ev-sequence-continue (restore env) (restore unev) (assign unev (op rest-exps) (reg unev)) (goto (label ev-sequence)) ev-sequence-last-exp (restore continue) (goto (label eval-dispatch)) ;; check different implementation in the book ev-if (save exp) ; save expression for later (save env) (save continue) (assign continue (label ev-if-decide)) (assign exp (op if-predicate) (reg exp)) (goto (label eval-dispatch)) ; evaluate the predicate ev-if-decide (restore continue) (restore env) (restore exp) (test (op true?) (reg val)) (branch (label ev-if-consequent)) ev-if-alternative (assign exp (op if-alternative) (reg exp)) (goto (label eval-dispatch)) ev-if-consequent (assign exp (op if-consequent) (reg exp)) (goto (label eval-dispatch)) ev-assignment (assign unev (op assignment-variable) (reg exp)) (save unev) ; save variable for later (assign exp (op assignment-value) (reg exp)) (save env) (save continue) (assign continue (label ev-assignment-1)) (goto (label eval-dispatch)) ; evaluate the assignment value ev-assignment-1 (restore continue) (restore env) (restore unev) (perform (op set-variable-value!) (reg unev) (reg val) (reg env)) (assign val (const ok)) (goto (reg continue)) ev-definition (assign unev (op definition-variable) (reg exp)) (save unev) ; save variable for later (assign exp (op definition-value) (reg exp)) (save env) (save continue) (assign continue (label ev-definition-1)) (goto (label eval-dispatch)) ; evaluate the definition value ev-definition-1 (restore continue) (restore env) (restore unev) (perform (op define-variable!) (reg unev) (reg val) (reg env)) (assign val (const ok)) (goto (reg continue))))) (define primitive-procedures (list (list 'car car) (list 'cdr cdr) (list 'cons cons) (list 'null? null?) (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)) (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)) (start eceval)
Exercise 5.23
Exercise:
Extend the evaluator to handle derived expressions such as `cond', `let', and so on (section *Note 4-1-2::). You may "cheat" and assume that the syntax transformers such as `cond->if' are available as machine operations. (fn: This isn't really cheating. In an actual implementation built from scratch, we would use our explicit-control evaluator to interpret a Scheme program that performs source-level transformations like `cond->if' in a syntax phase that runs before execution.)
Answer:
In order to add support for let
, I have:
Defined
cond?
andcond->if
:(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))))))
Added
cond?
andcond->if
to theeceval-operations
:(define eceval-operations (list (list 'self-evaluating? self-evaluating?) (list 'empty-arglist empty-arglist) ;; ... (list 'cond? cond?) (list 'cond->if cond->if) ;; ... (list 'define-variable! define-variable!)))
Added a
test
instruction and abranch
instruction to theeval-dispatch
section in the controller text:eval-dispatch (test (op self-evaluating?) (reg exp)) (branch (label ev-self-eval)) ;; ... (test (op cond?) (reg exp)) (branch (label ev-cond)) ;; ... (branch (label ev-application)) (goto (label unknown-expression-type))
Added an
ev-cond
section in the controller text:ev-sequence ;; ... ev-cond (assign exp (op cond->if) (reg exp)) (goto (label eval-dispatch)) ev-if ;; ...
I have done the same, mutatis mutandis, in order to add support for
let
:
(define (let? exp) (tagged-list? exp 'let)) (define (let-vars exp) (map car (cadr exp))) (define (let-exps exp) (map cadr (cadr exp))) (define (let-body exp) (cddr exp)) (define (named-let->combination exp) (let ((nameless (cons (car exp) (cddr exp))) (name (cadr exp))) (cons 'begin (list (cons 'define (cons (cons name (let-vars nameless)) (let-body nameless))) (cons name (let-exps nameless)))))) (define (let->combination exp) (if (not (pair? (cadr exp))) (named-let->combination exp) (cons (cons 'lambda (cons (let-vars exp) (let-body exp))) (let-exps exp)))) (define eceval-operations (list (list 'self-evaluating? self-evaluating?) ;; ... (list 'let? let?) (list 'let->combination let->combination) ;; ... (list 'define-variable! define-variable!))) (define eceval (make-machine '(exp env val proc argl continue unev) eceval-operations '( ;; ... eval-dispatch (test (op self-evaluating?) (reg exp)) (branch (label ev-self-eval)) (test (op variable?) (reg exp)) (branch (label ev-variable)) (test (op quoted?) (reg exp)) (branch (label ev-quoted)) (test (op assignment?) (reg exp)) (branch (label ev-assignment)) (test (op definition?) (reg exp)) (branch (label ev-definition)) (test (op if?) (reg exp)) (branch (label ev-if)) (test (op cond?) (reg exp)) (branch (label ev-cond)) (test (op let?) (reg exp)) (branch (label ev-let)) (test (op lambda?) (reg exp)) (branch (label ev-lambda)) (test (op begin?) (reg exp)) (branch (label ev-begin)) (test (op application?) (reg exp)) (branch (label ev-application)) (goto (label unknown-expression-type)) ;; ... ev-let (assign exp (op let->combination) (reg exp)) (goto (label eval-dispatch)) ;; ... )))
Exercise 5.24
Exercise:
Implement
cond
as a new basic special form without reducing it toif
. You will have to construct a loop that tests the predicates of successivecond
clauses until you find one that is true, and then useev-sequence
to evaluate the actions of the clause.
Answer:
I have:
Defined (and added to the machine) some operations:
(define (cond? exp) (tagged-list? exp 'cond)) (define (cond-clauses exp) (cdr exp)) (define (cond-predicate clause) (car clause)) (define (cond-actions clause) (cdr clause)) (define (cond-first-clause clauses) (car clauses)) (define (cond-rest-clauses clauses)
(define eceval-operations (list (list 'self-evaluating? self-evaluating?) ;; ... (list 'cond? cond?) (list 'cond-clauses cond-clauses) (list 'cond-first-clause cond-first-clause) (list 'cond-rest-clauses cond-rest-clauses) (list 'cond-predicate cond-predicate) (list 'cond-actions cond-actions) ;; ... (list 'define-variable! define-variable!)))
Updated the controller:
ev-cond (assign exp (op cond-clauses) (reg exp)) ;; get rid of cond tag (save env) ;; FIXME should I save the env in the loop? (save continue) ev-cond-loop (save exp) ;; save clauses for later ;; FIXME should I add a check for the else clause? ;; FIXME should I add a check for the case in which the clauses are an empty list? (assign continue (label ev-cond-decide)) (assign exp (op cond-first-clause) (reg exp)) ;; put predicate of first clause (assign exp (op cond-predicate) (reg exp)) ;; in exp (goto (label eval-dispatch)) ev-cond-decide (restore exp) (test (op true?) (reg val)) (branch (label ev-cond-actions)) (assign exp (op cond-rest-clauses) (reg exp)) (goto (label ev-cond-loop)) ev-cond-actions (restore continue) (restore env) (assign unev (op cond-first-clause) (reg exp)) ;; put clause's actions (assign unev (op cond-actions) (reg unev)) ;; in unev (save continue) ;; cf. ev-begin... (goto (label ev-sequence))
Exercise 5.26
Exercise:
Use the monitored stack to explore the tail-recursive property of the evaluator (section 5-4-2). Start the evaluator and define the iterative
factorial
procedure from section 1.2.1(define (factorial n) (define (iter product counter) (if (> counter n) product (iter (* counter product) (+ counter 1)))) (iter 1 1))Run the procedure with some small values of n. Record the maximum stack depth and the number of pushes required to compute n! for each of these values.
a. You will find that the maximum depth required to evaluate n! is independent of n. What is that depth?
b. Determine from your data a formula in terms of n for the total number of push operations used in evaluating n! for any n >= 1. Note that the number of operations used is a linear function of n and is thus determined by two constants.
Answer:
;;; EC-Eval input: (define (factorial n) (define (iter product counter) (if (> counter n) product (iter (* counter product) (+ counter 1)))) (iter 1 1)) (total-pushes = 3 maximum-depth = 3) ;;; EC-Eval value: ok ;;; EC-Eval input: (factorial 1) (total-pushes = 64 maximum-depth = 10) ;;; EC-Eval value: 1 ;;; EC-Eval input: (factorial 2) (total-pushes = 99 maximum-depth = 10) ;;; EC-Eval value: 2 ;;; EC-Eval input: (factorial 3) (total-pushes = 134 maximum-depth = 10) ;;; EC-Eval value: 6 ;;; EC-Eval input: (factorial 4) (total-pushes = 169 maximum-depth = 10) ;;; EC-Eval value: 24 ;;; EC-Eval input: (factorial 6) (total-pushes = 239 maximum-depth = 10) ;;; EC-Eval value: 720 ;;; EC-Eval input: (factorial 9) (total-pushes = 344 maximum-depth = 10) ;;; EC-Eval value: 362880 ;;; EC-Eval input: (factorial 12) (total-pushes = 449 maximum-depth = 10) ;;; EC-Eval value: 479001600
a: The depth is 10.
b: The difference between the stack operations needed for n!
and
those needed for (n+1)!
is 35.
The linear function can be written in the form ops(n!) = a * n + b
,
where a
is the rate at which operations increase (the slope) and b
is the base number of operations.
We know that a
is 35.
By substituting we can find the value of b
, which is 29.
So, this should be the formula: ops(n!) = 35n + 29
.
Exercise 5.27
Exercise:
For comparison with exercise 5.26, explore the behavior of the following procedure for computing factorials recursively:
(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n)))By running this procedure with the monitored stack, determine, as a function of n, the maximum depth of the stack and the total number of pushes used in evaluating n! for n >= 1. (Again, these functions will be linear.) Summarize your experiments by filling in the following table with the appropriate expressions in terms of n:
Maximum depth Number of pushes
Recursive factorial
Iterative factorial
The maximum depth is a measure of the amount of space used by the evaluator in carrying out the computation, and the number of pushes correlates well with the time required.
Answer:
;; monitoring the recursive factorial ;;; EC-Eval input: (define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) (total-pushes = 3 maximum-depth = 3) ;;; EC-Eval value: ok ;;; EC-Eval input: (factorial 1) (total-pushes = 16 maximum-depth = 8) ;;; EC-Eval value: 1 ;;; EC-Eval input: (factorial 2) (total-pushes = 48 maximum-depth = 13) ;;; EC-Eval value: 2 ;;; EC-Eval input: (factorial 3) (total-pushes = 80 maximum-depth = 18) ;;; EC-Eval value: 6 ;;; EC-Eval input: (factorial 4) (total-pushes = 112 maximum-depth = 23) ;;; EC-Eval value: 24 ;;; EC-Eval input: (factorial 5) (total-pushes = 144 maximum-depth = 28) ;;; EC-Eval value: 120 ;;; EC-Eval input: (factorial 6) (total-pushes = 176 maximum-depth = 33) ;;; EC-Eval value: 720 ;;; EC-Eval input: (factorial 7) (total-pushes = 208 maximum-depth = 38) ;;; EC-Eval value: 5040 ;;; EC-Eval input: (factorial 8) (total-pushes = 240 maximum-depth = 43) ;;; EC-Eval value: 40320 ;;; EC-Eval input: (factorial 9) (total-pushes = 272 maximum-depth = 48) ;;; EC-Eval value: 362880
- recursive factorial:
- number of pushes:
ops(n!) = 32 * n + 16
- Max depth:
max_depth(n!) = 5 * n + 3
- number of pushes:
Maximum depth | Number of pushes | |
---|---|---|
recursive | w | |
factorial | 5 * n + 3 | 32 * n + 16 |
Iterative | 10 | 35 * n + 29 |
factorial |
Exercise 5.28
Exercise:
Modify the definition of the evaluator by changing
eval-sequence
as described in section 5.4.2 so that the evaluator is no longer tail-recursive. Rerun your experiments from Exercise 5.26 and Exercise 5.27 to demonstrate that both versions of thefactorial
procedure now require space that grows linearly with their input.
Answer:
;;; EC-Eval input: (define (factorial n) (define (iter product counter) (if (> counter n) product (iter (* counter product) (+ counter 1)))) (iter 1 1)) (total-pushes = 3 maximum-depth = 3) ;;; EC-Eval value: ok ;;; EC-Eval input: (factorial 1) (total-pushes = 70 maximum-depth = 17) ;;; EC-Eval value: 1 ;;; EC-Eval input: (factorial 2) (total-pushes = 107 maximum-depth = 20) ;;; EC-Eval value: 2 ;;; EC-Eval input: (factorial 3) (total-pushes = 144 maximum-depth = 23) ;;; EC-Eval value: 6 ;;; EC-Eval input: (factorial 4) (total-pushes = 181 maximum-depth = 26) ;;; EC-Eval value: 24 ;;; EC-Eval input: (factorial 5) (total-pushes = 218 maximum-depth = 29) ;;; EC-Eval value: 120 ;;; EC-Eval input: (factorial 6) (total-pushes = 255 maximum-depth = 32) ;;; EC-Eval value: 720
;;; EC-Eval input: (define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) (total-pushes = 3 maximum-depth = 3) ;;; EC-Eval value: ok ;;; EC-Eval input: (factorial 1 ) (total-pushes = 18 maximum-depth = 11) ;;; EC-Eval value: 1 ;;; EC-Eval input: (factorial 2) (total-pushes = 52 maximum-depth = 19) ;;; EC-Eval value: 2 ;;; EC-Eval input: (factorial 3) (total-pushes = 86 maximum-depth = 27) ;;; EC-Eval value: 6 ;;; EC-Eval input: (factorial 4) (total-pushes = 120 maximum-depth = 35) ;;; EC-Eval value: 24 ;;; EC-Eval input: (factorial 5) (total-pushes = 154 maximum-depth = 43) ;;; EC-Eval value: 120 ;;; EC-Eval input: (factorial 6) (total-pushes = 188 maximum-depth = 51) ;;; EC-Eval value: 720
Exercise 5.29
Exercise:
Monitor the stack operations in the tree-recursive Fibonacci computation:
(define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))a. Give a formula in terms of
n
for the maximum depth of the stack required to computeFib(n)
forn >= 2
. Hint: In section 2.2 we argued that the space used by this process grows linearly withn
.b. Give a formula for the total number of pushes used to compute
Fib(n)
forn >= 2
. You should find that the number of pushes (which correlates well with the time used) grows exponentially withn
. Hint: LetS(n)
be the number of pushes used in computingFib(n)
. You should be able to argue that there is a formula that expressesS(n)
in terms ofS(n - 1)
,S(n - 2)
, and some fixed "overhead" constant k that is independent of n. Give the formula, and say whatk
is. Then show thatS(n)
can be expressed asaFib(n + 1) + b
and give the values ofa
andb
.
Answer:
;;; EC-Eval input: (define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) (total-pushes = 3 maximum-depth = 3) ;;; EC-Eval value: ok ;;; EC-Eval input: (fib 2) (total-pushes = 72 maximum-depth = 13) ;;; EC-Eval value: 1 ;;; EC-Eval input: (fib 3) (total-pushes = 128 maximum-depth = 18) ;;; EC-Eval value: 2 ;;; EC-Eval input: (fib 4) (total-pushes = 240 maximum-depth = 23) ;;; EC-Eval value: 3 ;;; EC-Eval input: (fib 5) (total-pushes = 408 maximum-depth = 28) ;;; EC-Eval value: 5 ;;; EC-Eval input: (fib 6) (total-pushes = 688 maximum-depth = 33) ;;; EC-Eval value: 8 ;;; EC-Eval input: (fib 7) (total-pushes = 1136 maximum-depth = 38) ;;; EC-Eval value: 13 ;;; EC-Eval input: (fib 8) (total-pushes = 1864 maximum-depth = 43) ;;; EC-Eval value: 21 ;;; EC-Eval input: (fib 9) (total-pushes = 3040 maximum-depth = 48) ;;; EC-Eval value: 34 ;;; EC-Eval input: (fib 10)
a.
max-depth(fib(n)) = 5n + 8
.b.
The first formula is
S(n) = S(n-1) + S(n-2) + 40
.The second one…?