Ex. 4.2
Louiseの主張は、evalを、このようにすること。
(define (eval exp env) (cond ((application? exp) ← application の節を先頭にする (apply (actual-value (operator exp) env) (operands exp) env)) ((self-evaluating? exp) exp) ...
よく使う節を先頭にして効率を上げるというのは正しい。が、それ以前にこれには不具合がある。
a. (define x 3) を評価すると、application? の結果が true になり、関数として評価されてしまう。
b. application? がちゃんと関数呼び出しを区別できるように、関数呼び出しには目印をつけることにする。
(call + 2 3)
とか。なので、application? をこう変える。
(define (application? exp) (tagged-list? exp 'call))
Ex. 4.3
データ主導流 → 関数表を作って処理の振り分けする(p.108)
省略...
Ex. 4.4
and と or を構文形式として導入する。
まず、eval の変更
(define (eval exp env) (cond ((self-evaluating? exp) exp) ... ((cond? exp)(eval (cond->if exp) env)) ((and? exp)(eval-and exp env)) ← この辺に and の処理追加 ((or? exp)(eval-or exp env)) ← この辺に or の処理追加 ...
追加した関数はこんな感じ。
(define (and? exp) (tagged-list? exp 'and)) (define (or? exp) (tagged-list? exp 'or)) (define (eval-and exps env) (cond ((null? exps) 'true) ((last-exp? exps) (eval (first-exp exps) env)) (else (if (not (true? (eval (first-exp exps) env))) 'false (eval-and (rest-exps exps) env))))) (define (eval-or exps env) (cond ((null? exps) 'false) (else (let ((a (eval (first-exp exps) env))) (if (true? a) a (eval-or (rest-exps exps) env))))))
次に、and と or を導出式として処理する方法。
if形式で置き換える。
and の置き換えは、こんな感じ。
(and a b)→
(if (a) (if (b) 'true 'false) 'false)
or の置き換え
(or a b)→
(if (a) 'true (if (b) 'true 'false))
実装は省略 (^^;
Ex. 4.5
cond の変な節。どこで使うのか?
expand-clauses を変更
(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 "ELSE clause isn't last -- COND->IF" clauses)) (let ((actions (cond-actions first))) ;; make-if の前に=>の処理を追加した (if (and (= 2 (length actions)) (eq? '=> (car actions))) (eval (cadr actions) env) (make-if (cond-predicate first) (sequence->exp (cond-actions first)) (expand-clause rest))))))))
Ex. 4.6
let形式の導入。方法は、letをlambdaに変換するということで、、、
lambdaへの変換の方法は、let の変数を lambda の仮引数にし、各値は lambdaを呼び出すときの引数にするという感じ。
まず、evalの変更点。
(define (eval exp env) (cond ((self-evaluating? exp) exp) ... ((cond? exp)(eval (cond->if exp) env)) ((and? exp)(eval-and exp env)) ((or? exp)(eval-or exp env)) ((let? exp)(eval (let->combination exp) env)) ← この辺に let 節追加 ...
他の追加関数は、以下の通り。
(define (let? exp) (tagged-list? exp 'let)) (define (let->combination exp) (let ((bindings (cadr exp)) (body (cddr exp))) (list (cons 'lambda (cons (map car bindings) body)) (map cadr bindings))))
Ex. 4.7
let*形式の導入。方針は、let* を let の入れ子に変換するということで、、、
入れ子にするのは、束縛の順番を指定するため。
変換は、こんな感じ
(let* ((x 1)(y 2)) (+ x y))→
(let ((x 1)) (let ((y 2)) (+ x y)))
eval の変更点
(define (eval exp env) (cond ((self-evaluating? exp) exp) ... ((let? exp)(eval (let->combination exp) env)) ((let*? exp)(eval (let*->nested-lets exp) env)) ← この辺に let* 節追加 ...
他に追加した関数
(define (let*? exp) (tagged-list? exp 'let*)) (define (let*->nested-lets exp) (let ((bindings (cadr exp)) (body (caddr exp))) (define (make-nest b) (if (null? b) body (list 'let (list (car b)) (make-nest (cdr b))))) (make-nest bindings)))
問題の最後の "...節を追加するだけで十分か..."云々という質問について。
質問の意図は、let*節は、let* を let に変換したまま放っておいていいのか? let をさらに lambda にする責任があるのではないのか?ということ
evalは再帰的に適用されるので、この質問にたいしては、"OK"という解答である。
Ex. 4.8
名前つき let
(define (let->combination exp) (if (list? (cadr exp)) ;; case that there is no name (let ((bindings (cadr exp)) (body (cddr exp))) (cons (cons 'lambda (cons (map car bindings) body)) (map cadr bindings))) (let ((var (cadr exp)) ;; ここ以下を追加 (bindings (caddr exp)) (body (cdddr exp))) (list 'let '() (list 'define (cons var (map car bindings)) body) ;; bug? (cons var (map cadr bindings))))))
バグってるっぽいが...
Ex. 4.9
for形式の設計。こんな感じ
(for 1 10 f)
1~10までの繰り返し。f は1変数関数。
これを、こんな風に置き換える
(let () (define (iter n) (if (not (= n 10)) (begin (f n) (iter (+ n 1))))) (iter 1))
実装は、、、省略。(^^ゞ
Ex. 4.10
データ抽象とは、データを作ったり、操作するのに、直接cons, car, cdrを使わない。
構成子、選択子という関数を介してアクセスすること。(p.46)
ここのプログラムに関して言えば、例えば、if式の判定を
(define (eval exp env) (cond ((self-evaluating? exp) exp) ... ((if? exp)(eval-if exp env)) ...
としていること。もしこれが、
(define (eval exp env) (cond ((self-evaluating? exp) exp) ... ((eq? 'if (car exp))(eval-if exp env)) ...
となっていたら、データ抽象とは言えない。
if式の構文を変更して、FORTRAN風の
"if x then a else b"
という文字列にしたとしても、データ抽象を使っていれば、eval と apply は変更不要である。
変更するのは、選択子だけ。こんな感じ。
(define (if? exp) (equal? (substring exp 1 2) "if")) (define (if-predicate exp) (substring 3 (string-scan exp "then"))) (define (if-consequent exp) (substring (string-scan exp "then") (string-scan exp "else"))) (define (if-alternative exp) (substring (string-scan exp "else")))
余計なことを言うなら、この章では、Scheme で Scheme を作っているだけなので、データ抽象の恩恵はない。オーバースペックである。むしろデータ抽象なしのほうが、コードの量が減ってうれしいかも。