SICP問題4.27
入力に対する応答内容を答える問題。
"← 応答"となっている部分が解答。
(define count 0) (define (id x) (set! count (+ count 1)) x) (define w (id (id 10))) ;;; L-Eval input: count ;;; L-Eval value: 1 ;; ← 応答 ;;; L-Eval input: w ;;; L-Eval value: 10 ;; ← 応答 ;;; L-Eval input: count ;;; L-Eval value: 2 ;; ← 応答
このままでは訳が分からないので、実行環境がどうなっているかを確認する。分かりやすくするため、thunkのメモ化は行っていない状態で確認する。
まずはcount, idの定義。
(define count 0) (define (id x) (set! count (+ count 1)) x)
the-global-environmentの内容は以下のとおり。
#0=(((id count false true car cdr cons null? = + - *) (procedure (x) ((set! count (+ count 1)) x) #0#) 0 #f #t (primitive #<subr car>) (primitive #<subr cdr>) (primitive #<subr cons>) (primitive #<subr null?>) (primitive #<subr =>) (primitive #<subr +>) (primitive #<subr ->) (primitive #<subr *>)))
id が
(procedure (x) ((set! count (+ count 1)) x) #0#)
countが
0
として登録されている。
次にwの定義。
(define w (id (id 10)))
the-global-environmentの内容は以下のとおり。
#0=(((w id count false true car cdr cons null? = + - *) (thunk (id 10) #0#) (procedure (x) ((set! count (+ count 1)) x) #0#) 1 #f #t (primitive #<subr car>) (primitive #<subr cdr>) (primitive #<subr cons>) (primitive #<subr null?>) (primitive #<subr =>) (primitive #<subr +>) (primitive #<subr ->) (primitive #<subr *>)) )
id が
(procedure (x) ((set! count (+ count 1)) x) #0#)
countが
1
wが
(thunk (id 10) #0#)
となっている。これは以下のような流れになっていると考えられる。
(define w (id (id 10)))
がevalに渡されると、definition?がtrueとなり
(eval-definition (define w (id (id 10))) env)
が実行される。eval-definitionでは
(define-variable! w (eval (id (id 10)) env) env)
が実行される。evalのcond節でapplication?がtrueとなるため
(define-variable! w (apply (actual-value id env) ((id 10)) env)
が実行される。actual-valueは更に
(define-variable! w (apply (force-it (eval id env)) ((id 10)) env))
となる。ここで(eval id env)はvariable?がtrueとなりlookup-variable-valueで環境から検索され
(define-variable! w (apply (force-it (procedure (x) ((set! count (+ count 1)) x) env)) ((id 10)) env))
となる。force-itの引数 (procedure〜 はthunkではないので、force-itでそのまま返され
(define-variable! w (apply (procedure (x) ((set! count (+ count 1)) x) env) ((id 10)) env))
となる。
applyのcond節ではcompound-procedure?がtrueとなるため
(eval-sequence (procedure-body (procedure (x) ((set! count (+ count 1)) x))) (extend-environment (procedure-prameters (procedure (x) ((set! count (+ count 1)) x))) (list-of-delayed-args ((id 10)) env) (procedure-environment (procedure (x) ((set! count (+ count 1)) x)))))
が実行される。これは
(eval-sequence ((set! count (+ count 1)) x) (extend-environment (x) ((thunk (id 10) env)) env))
となる。eva-sequenceでは
(eval (first-exp ((set! count (+ count 1)) x)) (extend-environment (x) ((thunk (id 10) env)) env)) (eval-sequence (rest-exp ((set! count (+ count 1)) x)) (extend-environment (x) ((thunk (id 10) env)) env))
が順番に実行される。最初のevalによってcountが+1される。
次のeval-sequenceはlast-exp?がtrueとなるため
(eval (first-exp (x)) (extend-environment (x) ((thunk (id 10) env)) env))
となる。evalではvariable?がtrueとなるため、lookup-variable-valueが実行され、環境に登録された(thunk (id 10))が返され、これがeval-sequenceの結果となる。
長くなったけど、結論。
defineが実行された時点で(id (id 10))の外側のidが評価され、countの値が+1される。
内側の(id 10)についてはthunk化されるだけで実行されない。
よってwの定義時点でcountは1となる。
wを実行した際にはthunkが評価され、countが+1された後、引数の10を返す。よってこの後countを確認すると2が返って来る。
ちなみにメモ化していない版の場合、これ以降もwを実行する度にcountの値は+1されて変化し続けるが、メモ化している場合は最初のwを実行した後はthunkからevaluated-thunkに変換され、評価が行われなくなるため値は2から変化しなくなる。