SICP 4.1.2 Ex. 4.2 Ex. 4.3 Ex. 4.4 Ex. 4.5 Ex. 4.6 Ex. 4.7 Ex. 4.8 Ex. 4.9 Ex. 4.10

  • 投稿日:
  • カテゴリ:

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