Exercise 5.21. Implement register machines for the following procedures. Assume that the list-structure memory operations are available as machine primitives.
a. Recursive count-leaves:
(define (count-leaves tree)
(cond ((null? tree) 0)
((not (pair? tree)) 1)
(else (+ (count-leaves (car tree))
(count-leaves (cdr tree))))))
b. Recursive count-leaves with explicit counter:
(define (count-leaves tree)
(define (count-iter tree n)
(cond ((null? tree) n)
((not (pair? tree)) (+ n 1))
(else (count-iter (cdr tree)
(count-iter (car tree) n)))))
(count-iter tree 0))
写 controller 的关键在于理解 continue 以及如何使用 stack 来保存返回值。
写这一题如果困难,可以参考 Fibonacci machine。
(define controller-a
'((assign continue (label count-done))
count-loop
(test (op null?) (reg tree))
(branch (label base-case0))
(assign t (op pair?) (reg tree))
(test (op not) (reg t))
(branch (label base-case1))
(save continue)
(assign continue (label count-after-car))
(save tree)
(assign tree (op car) (reg tree))
(goto (label count-loop))
count-after-car
(restore tree)
(assign tree (op cdr) (reg tree)) ; prepare for (count-leaves (cdr tree))
(save val) ; value of (count-leaves (car tree))
(assign continue (label count-after-cdr))
(goto (label count-loop))
count-after-cdr
(assign tree (reg val)) ; val stores value of (count-leaves (cdr tree))
(restore val) ; get value of (count-leaves (car tree))
(restore continue)
(assign val (op +) (reg tree) (reg val)) ; put the result in val
(goto (reg continue))
base-case0
(assign val (const 0))
(goto (reg continue))
base-case1
(assign val (const 1))
(goto (reg continue))
count-done
))
然后进行测试
(define machine-a
(make-machine
'(tree t continue val)
(list (list 'null? null?)
(list 'pair? pair?)
(list 'not not)
(list 'car car)
(list 'cdr cdr)
(list '+ +))
controller-a))
(define (count-leaves tree)
(cond ((null? tree) 0)
((not (pair? tree)) 1)
(else (+ (count-leaves (car tree))
(count-leaves (cdr tree))))))
;; tests for machine-a begin
(load "../testframe.scm")
(let ((tree '((1 (2 3 4)) ((5) (6) (((7)))))))
(set-register-contents! machine-a 'tree tree)
(start machine-a)
(assert= (get-register-contents machine-a 'val)
(count-leaves tree)))
对于 b,我们应该这样设计
(define controller-b
'((assign n (const 0))
(assign continue (label count-done))
count-loop
(test (op null?) (reg tree))
(branch (label base-case0))
(assign t (op pair?) (reg tree))
(test (op not) (reg t))
(branch (label base-case1))
(save continue)
(assign continue (label count-after-car))
(save tree)
(assign tree (op car) (reg tree))
(goto (label count-loop))
count-after-car
(restore tree)
(assign tree (op cdr) (reg tree))
(assign continue (label count-after-cdr))
(goto (label count-loop))
count-after-cdr
(restore continue)
base-case0
;; n value is kept in reg n
(goto (reg continue))
base-case1
(assign n (op +) (reg n) (const 1))
(goto (reg continue))
count-done
))
然后进行测试
(define machine-b
(make-machine
'(tree t continue n)
(list (list 'null? null?)
(list 'pair? pair?)
(list 'not not)
(list 'car car)
(list 'cdr cdr)
(list '+ +))
controller-b))
(define (count-leaves tree)
(define (count-iter tree n)
(cond ((null? tree) n)
((not (pair? tree)) (+ n 1))
(else (count-iter (cdr tree)
(count-iter (car tree) n)))))
(count-iter tree 0))
;; tests for machine-b begin
(load "../testframe.scm")
(let ((tree '((1 (2 3 4)) ((5) (6) (((7)))))))
(set-register-contents! machine-b 'tree tree)
(start machine-b)
(assert= (get-register-contents machine-b 'n)
(count-leaves tree)))
使用了 explicit counter 之后,我们的指令大幅度减少了。对比 count-after-cdr 的代码,b 里面只需要 restore continue 就可以,而 machine a 里面,count-after-cdr 的代码则要从栈上取出之前的计算结果。machine b 的优势在于所有的结果都直接放到 n 里面,不需要借助栈来保存结果。