SICP 読書ノート#69 - 4.4.4 質問システムの実装(3) (pp.278-292)
§4.4.4の練習問題から。
問題 4.70
以下のadd-assertion!
の実装の何が悪いかを説明せよ。
(define (add-assertion! assertion) (store-assertion-in-index assertion) (set! THE-ASSERTIONS (cons-stream assertion THE-ASSERTIONS)) 'ok)
オリジナルの実装は以下の通り。
(define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok))
新しいadd-assertion!
の実装を見せられると
オリジナルの方のold-assertions
が冗長に見えるがそんなことはない。
新しい実装ではTHE-ASSERTIONS
が循環してしまっているため、
stream-ref
の結果が同じassertion
になってしまう。
(stream-ref THE-ASSERTIONS 0) => assertion (stream-ref THE-ASSERTIONS 1) => assertion (stream-ref THE-ASSERTIONS 2) => assertion ...
オリジナルの実装ではold-assertions
をたどっていくため正しく機能する。
(stream-ref THE-ASSERTIONS 0) => assertion (stream-ref THE-ASSERTIONS 1) => (stream-car old-assertions) ...
問題 4.71
query systemの以下の手続きをLouisの提案通りに入替えてみた。
(define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append (find-assertions query-pattern frame) (apply-rules query-pattern frame))) frame-stream)) (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave (qeval (first-disjunct disjuncts) frame-stream) (disjoin (rest-disjuncts disjuncts) frame-stream))))
普通の合成質問や規則であれば問題なく動く。
;;; Query input: (or (supervisor ?who (Bitdiddle Ben)) (supervisor ?who (Hacker Alyssa P))) ;;; Query results: (or (supervisor (Tweakit Lem E) (Bitdiddle Ben)) (supervisor (Tweakit Lem E) (Hacker Alyssa P))) (or (supervisor (Reasoner Louis) (Bitdiddle Ben)) (supervisor (Reasoner Louis) (Hacker Alyssa P))) (or (supervisor (Fect Cy D) (Bitdiddle Ben)) (supervisor (Fect Cy D) (Hacker Alyssa P))) (or (supervisor (Hacker Alyssa P) (Bitdiddle Ben)) (supervisor (Hacker Alyssa P) (Hacker Alyssa P))) ;;; Query input: (lives-near ?who (Bitdiddle Ben)) ;;; Query results: (lives-near (Aull DeWitt) (Bitdiddle Ben)) (lives-near (Reasoner Louis) (Bitdiddle Ben))
ではdelay
させている理由とは何か?
考えてもわからない… そもそも有限のデータベースやフレームしか扱わない時点で ストリームを使う理由すらよくわからない。単純なリストでも実装できそうな気がするが。。
わからないのでググってみると、以下のブログが参考になりました。
なるほど。無限ループになるような質問の時の振る舞いが異なるとのこと。
例えば、orクエリで片方の質問は解が得られるがもう一方は無限ループとなるようなクエリで、
元々のdelay
をさせている場合は、無限ループにハマるものの解はプリントされる。
;;; Query input: (or (married Mickey ?x) (job ?y (computer programmer))) ;;; Query results: (or (married Mickey ?who) (job ?y (computer programmer))) (or (married Mickey ?x) (job (Fect Cy D) (computer programmer))) (or (married Mickey ?who) (job ?y (computer programmer))) (or (married Mickey ?x) (job (Hacker Alyssa P) (computer programmer))) (or (married Mickey ?who) (job ?y (computer programmer))) (or (married Mickey ?who) (job ?y (computer programmer))) ;;=> 以降ずっと(or (job ?x (computer programmer)) (married Mickey ?who))のプリントが続く... user break
一方、Louisの提案の場合は、delay
がない分
instantiate
までたどり着く前に無限ループにはまり続けるので、何もプリントされない。
;;; Query input: (or (married Mickey ?x) (job ?y (computer programmer))) ;;=> 何もプリントされない user break
問題 4.72
問題4.71 で見た通り、orクエリで一方が無限ループに入るようなクエリでも
interleave
で差し込みにしておくことでもう一方は表示されるから。
問題 4.73
delay
を与えないflatten-stream
の場合、
(define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave (stream-car stream) (flatten-stream (stream-cdr stream)))))
引数stream
が無限ストリームの場合は返ってこなくなるから。
ただしquery systemでは、データベースのassertionsやrulesが有限であるため
上記のflatten-stream
に入れ替えても正しく動作する。
問題 4.74
a.
simple-stream-flatmap
の実装は以下の通り。
(define (simple-stream-flatmap proc s) (simple-flatten (stream-map proc s))) (define (simple-flatten stream) (stream-map stream-car (stream-filter (lambda (s) (not (stream-null? s))) stream)))
動作確認。データベースに対し(job ?x ?y)
のパターンで取得した
assertionsをjob-assertions
とする。
racket@ch4-query.scm> (define job-assertions (fetch-assertions '(job (? x) (? y)) (singleton-stream '()))) racket@ch4-query.scm> (stream->list job-assertions) '((job (Aull DeWitt) (administration secretary)) (job (Cratchet Robert) (accounting scrivener)) (job (Scrooge Eben) (accounting chief accountant)) (job (Warbucks Oliver) (administration big wheel)) (job (Reasoner Louis) (computer programmer trainee)) (job (Tweakit Lem E) (computer technician)) (job (Fect Cy D) (computer programmer)) (job (Hacker Alyssa P) (computer programmer)) (job (Bitdiddle Ben) (computer wizard)))
find-assertions
でパターンの変数の束縛結果からフレームストリームを生成する処理について。
ここでstream-flatmap
を使ってる。
racket@ch4-query.scm> (stream->list (stream-flatmap (lambda (datum) (check-an-assertion datum '(job (? x) (? y)) '())) job-assertions)) '((((? y) administration secretary) ((? x) Aull DeWitt)) (((? y) accounting scrivener) ((? x) Cratchet Robert)) (((? y) accounting chief accountant) ((? x) Scrooge Eben)) (((? y) administration big wheel) ((? x) Warbucks Oliver)) (((? y) computer programmer trainee) ((? x) Reasoner Louis)) (((? y) computer technician) ((? x) Tweakit Lem E)) (((? y) computer programmer) ((? x) Fect Cy D)) (((? y) computer programmer) ((? x) Hacker Alyssa P)) (((? y) computer wizard) ((? x) Bitdiddle Ben)))
この処理をsimple-stream-flatmap
に置き換えてみても結果は同じ。
racket@ch4-query.scm> (stream->list (simple-stream-flatmap (lambda (datum) (check-an-assertion datum '(job (? x) (? y)) '())) job-assertions)) '((((? y) administration secretary) ((? x) Aull DeWitt)) (((? y) accounting scrivener) ((? x) Cratchet Robert)) (((? y) accounting chief accountant) ((? x) Scrooge Eben)) (((? y) administration big wheel) ((? x) Warbucks Oliver)) (((? y) computer programmer trainee) ((? x) Reasoner Louis)) (((? y) computer technician) ((? x) Tweakit Lem E)) (((? y) computer programmer) ((? x) Fect Cy D)) (((? y) computer programmer) ((? x) Hacker Alyssa P)) (((? y) computer wizard) ((? x) Bitdiddle Ben)))
b.
問題 4.73で見たように元のflatten-stream
にdelay
が必要なのは無限ストリームを扱うため。
simple-flatten
はdelay
がなくとも無限ストリームを扱えるため、システムの振る舞いは変わらない。
問題 4.75
ヒントの通りnegate
フィルタを参考にuniquely-asserted
を実装する。
(unique <query-pattern>)
について<query-pattern>
と一致する
assertionsやrulesをqeval
をつかって取得するには、以下のようにすればよい。
(define (uniquely-query exps) (car exps)) (let ((result-stream (qeval (uniquely-query contents) (singleton-stream frame)))) ...)
このresult-streamで得られるフレームは唯一の場合のみ、
出力フレームストリームとして返すようにuniquely-asserted
を実装する。
(define (uniquely-query exps) (car exps)) (define (uniquely-asserted contents frame-stream) (stream-flatmap (lambda (frame) (let ((result-stream (qeval (uniquely-query contents) (singleton-stream frame)))) (if (and (not (stream-null? result-stream)) (stream-null? (stream-cdr result-stream))) result-stream the-empty-stream))) frame-stream)) ;;さらに (put 'unique 'qeval uniquely-asserted) で手続きを追加...
テスト。たぶん動いていそう。
;;; Query input: (unique (job ?x (computer wizard))) ;;; Query results: (unique (job (Bitdiddle Ben) (computer wizard))) ;;; Query input: (unique (job ?x (computer programmer))) ;;; Query results: ;;; Query input: (and (job ?x ?j) (unique (job ?anyone ?j))) ;;; Query results: (and (job (Aull DeWitt) (administration secretary)) (unique (job (Aull DeWitt) (administration secretary)))) (and (job (Cratchet Robert) (accounting scrivener)) (unique (job (Cratchet Robert) (accounting scrivener)))) (and (job (Scrooge Eben) (accounting chief accountant)) (unique (job (Scrooge Eben) (accounting chief accountant)))) (and (job (Warbucks Oliver) (administration big wheel)) (unique (job (Warbucks Oliver) (administration big wheel)))) (and (job (Reasoner Louis) (computer programmer trainee)) (unique (job (Reasoner Louis) (computer programmer trainee)))) (and (job (Tweakit Lem E) (computer technician)) (unique (job (Tweakit Lem E) (computer technician)))) (and (job (Bitdiddle Ben) (computer wizard)) (unique (job (Bitdiddle Ben) (computer wizard))))
問題 4.76-79
ここまでquery systemの概要をさらっと見た程度では解けなさそうなのでパスします。 少なくとも問題 4.76ではユニフィケーションの細部を理解する必要がありそうだが、 そこまでできていない。。。
これで4章が終わったので、次回はまとめます。