SICPゼミ第21回
練習問題3.31
実行環境 DrRacket
> (define input-1 (make-wire )) (define input-2 (make-wire )) (define sum (make-wire )) (define carry (make-wire )) > (probe 'sum sum) > (probe 'carry carry) > (half-adder input-1 input-2 sum carry) 'ok > (propagate) 'done > (set-signal! input-1 1) 'done > (propagate) 'done > (get-signal input-1) 1 > (get-signal sum) 0 > (set-signal! input-2 1) 'done > (propagate) carry 11 New-value = 1'done > (get-signal sum) 0 > (get-signal input-1) 1
probe
のときに出力が出ないのは、probe
でadd-action!
が呼ばれても実行はされないというだけなので、それはそう。
こちらの記事
では、アジェンダが空だといっているが、そんなことはない。なぜなら、 set-signal!
を実行したときにcall-each
が呼ばれ、アジェンダに手続きが追加はされている。アジェンダの中身を見てみたのだろうか?
しかも、アジェンダの中身が空だとして、set-signal! input-2 1
を実行したときにcarry
が更新されているのはなぜだろうか?
以上のことから、アジェンダが空であるという説明は間違っている。ではなぜ実行結果がこうなるのだろうか?
半加算器を設定したときに初期化がされていないので、教科書の図3.25の配線eが0のまま更新されない。そのためset-signal!
してアジェンダに手続きが追加されてもワイヤーSの値への入力が間違ったままであるため、サムの値が0のまま更新されない。そのためaction-procedures
が呼ばれず、probe による値の表示も行われないというわけである。
2017/01/09 追記
上で言及したこちらの記事
に追記がなされていたが気づくのが非常に遅れてしまい申し訳ない限りである。
まず、こちらの記事にて言葉足らずであったところを補足させていただく。
大前提として、(propagate)
は真っ先にthe-agenda
が空であるかどうかの判定をしており、空であると何も実行せずに終わってしまう。つまりwire
にセットされた信号が正しく計算されていくにはthe-agenda
に各種手続き (output
のwire
の値を更新しろ、とか、(probe)
した wire
の値が更新されたら表示しろ、とか) が追加されていなくてはならない。the-agenda
を更新する手続きはafter-delay
のみであるため、各種手続きをラムダ閉包として引数にとったafter-delay
が実行されなくては(propagate)
が正しく作動しない。
さて、
(half-adder input-1 input-2 sum carry)
を実行したとき、つまり半加算器を結線したときに、and-gate
や or-gate
, inverter
の結線によって、各wire
のもつaction-procedures
にafter-delay
が追加されていく。
ここでaccept-action-procedure
が本来の
(define (accept-action-procedure! proc) (set! action-procedures (cons proc action-procedures)) (proc))
であれば、各wire
のもつaction-procedure
にafter-delay
が追加された後、(proc)
という実行によってafter-delay
が実行され、the-agenda
が更新されてゆく。つまり、その後の(propagate)
によって正しく計算が進んでゆく。
しかし、問題で述べられているような
(define (accept-action-procedure! proc) (set! action-procedures (cons proc action-procedures)))
であった場合にどうなるのか。after-delay
は各wire
のもつaction-procedure
に追加されるのみで、実行されない。つまりthe-agenda
は空のままである。このせいで(propagate)
をしても何も起こらない、だからちゃんと(proc)
を追加しなければならない、というのはもちろん正しい。
しかし、本記事で述べている例を見てみよう。
> (set-signal! input-2 1) 'done > (propagate) carry 11 New-value = 1'done
set-signal!
の後、(propagate)
することによってちゃんとcarry
の値が更新されているのである。
ではこれはなぜか?
実は、set-signal!
手続きにカギがある。
下は set-signal!
によって呼び出されるset-my-signal!
である。
(define (set-my-signal! new-value) (if (not (= signal-value new-value )) (begin (set! signal-value new-value) (call-each action-procedures )) 'done ))
set-my-signal!
の中で、「signal-value
とnew-value
が異なれば」call-each
によってaction-procedures
の中身が実行されるのである。つまり、各wire
のもつaction-procedures
に追加されていただけのafter-delay
たちが順に実行されていき、the-agenda
が更新されているということである。
ただし、ここで重要なのはこのset-my-signal
の初めに行われているif条件分岐、上で述べた「signal-value
とnew-value
が異なれば」というところである。
本来であれば一度どこかのwire
のもつaction-procedures
にafter-delay
が追加されれば、それが実行されthe-agenda
が更新されるはずなのであるが、今回のように即時実行しないaccept-action-procedure!
であると、set-signal!
によって「値が変更されたwire
のもつaction-procedures
に存在するafter-delay
のみ」が実行されていき、これらのみについてthe-agenda
が更新されていく、ということになる。
具体例を見ていこう。
上のような半加算器 (初期値は全て0) に対し、A(上の実行例ではinput-1
) に値1をset
したときに何がおこるだろうか。
> (set-signal! input-1 1) 'done > (propagate) 'done > (get-signal input-1) 1 > (get-signal sum) 0
Aに関連したD, C についてはand
,or
の演算が行われる。
Dはor-gate
なので 1 に更新される。しかし C はand-gate
なので0のまま更新されない。
本来1となるべき E の値をset
するinverter
手続きは C のaction-procedures
に眠ったままでthe-agenda
に追加されず、E の値は更新されないままである。
このせいでand-gate
である S に対する入力は1,0となり、S は0となる。つまり S も C も更新されない。そのためprobe
手続きも呼ばれない。結果返ってくるのは虚しい'done
の文字列だけである。
さて、次に B (上の実行例ではinput-2
) に値1をset
するとどうなるか。
> (set-signal! input-2 1) 'done > (propagate) carry 11 New-value = 1'done > (get-signal sum) 0 > (get-signal input-1) 1
B に関連した C, D について演算が行われる。
D は1のまま更新されない。C は1に更新される。C が更新されたためinverter
による値の更新が行われ、 E が 0 に更新される。S は0のまま更新されない。更新された C についてはprobe
が呼ばれる。これで全てのつじつまが合う、ということになる。
さて、以上が本記事で述べていた内容であるが、この追記なしでは非常にわかりづらい文章となっていたことに対して深く申し訳ないと思う。
ここで、上で言及したこちらの記事
の説明について、本追記の上半分
after-delay
は各wire
のもつaction-procedure
に追加されるのみで、実行されない。つまりthe-agenda
は空のままである。このせいで(propagate)
をしても何も起こらない、だからちゃんと(proc)
を追加しなければならない
という部分の説明しかなされていないように思ったので指摘させていただいた。もし本追記で述べた内容を全て踏まえた上での説明であったのであれば謝罪させていただく。