[SICP] 問題4.51
計算機プログラムの構造と解釈
posted with amazlet at 08.11.10
permanent-set! をどのように実装したらいいかを考えました。
そもそも、この元にすべきset! はどうだったのかな?
set! はch4-mceval.scm で実装されていて、評価器に組み込みの特殊形式でした。
よって、ch4-mceval.scm を参考に、次のように評価器に組み込みを開始します。
(define (analyze exp) (cond ((self-evaluating? exp) (analyze-self-evaluating exp)) ((quoted? exp) (analyze-quoted exp)) ((variable? exp) (analyze-variable exp)) ((permanent-assignment? exp) (analyze-permanent-assignment exp)) ;; これを追加! ((assignment? exp) (analyze-assignment exp)) ((definition? exp) (analyze-definition exp)) ((if? exp) (analyze-if exp)) ((lambda? exp) (analyze-lambda exp)) ((begin? exp) (analyze-sequence (begin-actions exp))) ((cond? exp) (analyze (cond->if exp))) ((let? exp) (analyze (let->combination exp))) ;** ((amb? exp) (analyze-amb exp)) ;** ((application? exp) (analyze-application exp)) (else (error "Unknown expression type -- ANALYZE" exp)))) ;; こちらも追加 (define (permanent-assignment? exp) (tagged-list? exp 'permanent-set!)) (define (permanent-assignment-variable exp) (cadr exp)) (define (permanent-assignment-value exp) (caddr exp))
この評価を実行する、analyze-permanent-assignment を定義します。
set! は失敗継続を扱うようにわざわざ工夫されたものなので、その"やり戻し" の部分を無効にしたものとして作ればよいはず。
set! の評価を実行するanalyze-assignment(P.257) を参考に、次のように作ってみました。
;; set! のanalyze-assignment (参考にする方) (define (analyze-assignment exp) (let ((var (assignment-variable exp)) (vproc (analyze (assignment-value exp)))) (lambda (env succeed fail) (vproc env (lambda (val fail2) ; *1* (let ((old-value (lookup-variable-value var env))) (set-variable-value! var val env) (succeed 'ok (lambda () ; *2* (set-variable-value! var ;; ここでやり戻し処理を行っている old-value env) (fail2))))) fail)))) ;; 新しく用意したもの (define (analyze-permanent-assignment exp) (let ((var (permanent-assignment-variable exp)) (vproc (analyze (permanent-assignment-value exp)))) (lambda (env succeed fail) (vproc env (lambda (val fail2) (begin (set-variable-value! var val env) (succeed 'ok fail2))) ;; やり戻しをしないようにごっそり削除 fail))))
準備できた。
問題にあるコードを流してみる。
(require とan-element-of は動作に際して必要なので実際に流すコード全体は次の通りです)
(define (require p) (if (not p) (amb))) (define (an-element-of items) (require (not (null? items))) (amb (car items) (an-element-of (cdr items)))) (define count 0) (let ((x (an-element-of '(a b c))) (y (an-element-of '(a b c)))) (permanent-set! count (+ count 1)) (require (not (eq? x y))) (list x y count))
実行結果はこうなりました。
;;; Starting a new problem ;;; Amb-Eval value: (a b 2) ;;; Amb-Eval input: try-again ;;; Amb-Eval value: (a c 3) ;;; Amb-Eval input: try-again ;;; Amb-Eval value: (b a 4) ;;; Amb-Eval input: try-again ;;; Amb-Eval value: (b c 6) ;;; Amb-Eval input: try-again ;;; Amb-Eval value: (c a 7) ;;; Amb-Eval input: try-again ;;; Amb-Eval value: (c b 8) ;;; Amb-Eval input: try-again ;;; There are no more values of (let ((x (an-element-of '(a b c))) (y (an-element-of '(a b c)))) (permanent-set! count (+ count 1)) (require (not (eq? x y))) (list x y count))
うまく動いている!
続いてもう一つの問いについても答える。
「permanent-set! の代わりにset! を使ったらどうなるか?」ということだが、set! のやり戻しが発生するため、count 値がそのたびに1 に戻るはずだ。
やってみよう。
;;; Starting a new problem ;;; Amb-Eval value: (a b 1) ;;; Amb-Eval input: try-again ;;; Amb-Eval value: (a c 1) ;;; Amb-Eval input: try-again ;;; Amb-Eval value: (b a 1)
できた。