Ex. 4.76
SQLでいうところの、表結合での索引検索と全表検索のことである。
ふつうは、A and B の Aの結果とBの結果がともに少ないときは全表検索が有利である。Aの結果とBの結果がある程度多いときは索引検索が有利になる。しかし、queryシステムには索引がないので、例えば (?x . Adam) をキーに表を絞り込むということができない。結局全表検索になる。なので最初から全表検索&結合のほうが有利である。
方針: A and B について
- A のフレームストリームを取得する (a1 a2 a3 ...)
- B のフレームストリームを取得する (b1 b2 b3 ...)
- フレーム a1と(b1 b2 b3 ...) の各フレームを結合可能なら結合する。((a1 b1) (a1 b3) ...)とか
- フレーム a2と(b1 b2 b3 ...) の各フレームを結合可能なら結合する。((a2 b2) ...)とか
- ...
- 上の結合結果をまとめて結合する ( (a1 b1) (a2 b2) (a1 b3) ... ) ← interleaveで結合する
1, 2の A と B を独立に実行し、6 で interleave でまとめるのは or つまり disjoin をまねして作ればよい。
難しそうなのは、3, 4, 5 ... の部分である。問題のヒントに従って unify-match のまねをして作る。
unify-match の復習
(unify-match '(son Adam ?x)① '(son Adam Cain)② '()③) ↓ son① と son② を比較。一致するので ()③ をそのまま返す。 ↓ Adam① と Adam② を比較。一致するので ()③ をそのまま返す。 ↓ ?x① と Cain② を比較。変数なので extend-if-possible を呼び出す。 これはフレーム拡張の可否を判定して拡張する。 ((?x . Cain))③ を返す。 ↓ 結果 ((?x . Cain))③ となる。
拡張できない場合
(unify-match '(son Adam ?x)① '(son Adam Cain)② '((?x . Enoch))③) ↓ son、Adamの処理(上と同じ) ↓ ?x① と Cain② を比較。変数なので extend-if-possible を呼び出す。 これはフレーム拡張の可否を判定してから拡張する。 ?x はすでに他の値に束縛 ((?x . Enoch))③されているので拡張不可と判定。 ↓ 結果 failed となる。
rule とユニファイする場合
(unify-match '(son Adam ?x)① '(son ?m ?s)② '()③) ↓ son① と son②は同じなのでフレームそのまんま。 ↓ Adam① と ?m②を比較。変数なので extend-if-possible を呼び出す。 フレームを拡張して ((Adam . ?1m))③ を返す。 ↓ ?x① と ?s②を比較。変数なので extend-if-possible を呼び出す。 フレームを拡張して ((Adam . ?1m) (?x . ?1s))③を返す。 ↓ 結果 ((Adam . ?1m) (?x . ?1s))③ となる。
このあと rule 本体の適用があるが、それは unify-match の仕事ではない。
conjoin への利用
conjoin で利用できるのは、extend-if-possible のフレーム拡張の可否判定の部分。 これが、a1 と b1 の矛盾判定に使える。、、、と思ったけどあんまり使えなかった。
A と Bの各々を組み合わせは2重ループになる。stream-flatmap を2重にして表現する。
こんな感じ
(define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (let ((stream-A (qeval (first-conjunct conjuncts) frame-stream)) (stream-B (qeval (car (rest-conjuncts conjuncts)) frame-stream))) (stream-flatmap (lambda (frame-a) (stream-flatmap (lambda (frame-b) (join-a-and-b frame-a frame-b)) stream-B)) stream-A)))) (define (join-a-and-b frame-a frame-b) (define (check-loop b) (if (eq? b '()) #t (let ((var (binding-variable (car b)))) (let ((binding-a (recursive-search-in-frame var frame-a)) (binding-b (recursive-search-in-frame var frame-b))) (cond ((eq? binding-a #f) (check-loop (cdr b))) ((eq? binding-b #f) (check-loop (cdr b))) ((equal? (binding-value binding-a) (binding-value binding-b)) (check-loop (cdr b))) (else #f)))))) (if (check-loop frame-b) (singleton-stream (append frame-a frame-b)) the-empty-stream)) (define (recursive-search-in-frame variable frame) (let ((binding (assoc variable frame))) (cond ((eq? binding #f) #f) ((var? (binding-value binding)) (recursive-search-in-frame (binding-value binding) frame)) (else binding))))
A と Bを独立に実行して、互いに矛盾のないフレームを結合する → この仕様自体が A 、B を無限ストリームであることを許さない(無限ストリームが矛盾してないかどうかは判定不可なので)。なので、check-loop は有限長のストリーム用である。
変数束縛の矛盾の有無の判定は (check-loopの中の判定文)
AとBの変数の束縛チェーンが最終的に異なる値に束縛されてたらNG それ以外はOK 例えば、Aの変数の束縛チェーンが最終的に値に束縛されていなければ、それでOK とか
という感じにした。間違ってるかも。変数の束縛チェーンが値を含まない場合でも、変数同士だけで矛盾することがある?あまり、良くわかっていない。
あと、recursive-search-in-frame をループの度に実行とか、計算コスト減っているのでしょうか。
ちょっとだけテスト
;;; Query input: (and (job ?a ?b) (same ?a (Bitdiddle Ben))) ;;; Query results: (and (job (Bitdiddle Ben) (computer wizard)) (same (Bitdiddle Ben) (Bitdiddle Ben))) ------ ;;; Query input: (and (job ?a ?b) (same (Bitdiddle Ben) ?a)) ;;; Query results: (and (job (Bitdiddle Ben) (computer wizard)) (same (Bitdiddle Ben) (Bitdiddle Ben)))