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 を作っているだけなので、データ抽象の恩恵はない。オーバースペックである。むしろデータ抽象なしのほうが、コードの量が減ってうれしいかも。