タグ「SICP」が付けられているもの

SICP 4.4.4.5 Ex. 4.79

  • 投稿日:
  • カテゴリ:

Ex. 4.79

束縛変数をすべて局所的環境におきかえる。なんていう壮大な計画は没。

変数の名前を替える(rename-variables-in)を呼び出していたのは、ruleの本体の処理 (apply-a-rule) のところである。

この処理だけ、局所的環境を使うようにする。また、束縛変数を1つづつではなく、frame ごと局所的環境におくようにする。

ブロック構造の手続きというのは、処理をサブルーチンに分けること

(sub 
   (sub1 ...)
   (sub2 ...))

こういうの。queryシステムだと、rule の中から別の rule を呼び出すこと。

実際に、名前の衝突が起こるのは、rule呼び出しの引数名しかない。(ruleにローカル変数ないし)

ここから、問題が別になっている。

(原文 "Can you relate any of this to the problem of ..." 局所的環境版でも名前変更版でも、どちらかを使って、次の問題を解け、、、)

"文脈の中で推論"について

「P が真ならば A B を推論する」というのは、AとBに関するルール

ルール①PならばA
       (rule1 A
              P)
ルール②PならばB
       (rule2 B
              P)

があるとき、Pを真にするフレーム (...frameP...) のもとでこれらのルールを評価するということである。

文脈というのは、この frameP を指すと思われる。

frameP のもとで推論可能な ルール①、ルール② が最初から選ばれているわけではないので、全ルールを総なめして、ルール①、ルール② を探さなければいけない。

ここで要求されているのは、このルールの検索プログラムを作れということだと思われる。

と思ったけど、これだけだと未解決でもなんでもない唯の検索問題になる、、、?

ということで、未解決問題"AIのフレーム問題"に絡めてみよう。(これしか知らんし)

例えば、rule が 10000個もあったら全ルールを総なめにするというのはとても重い作業だ。

さらに推論を重ねる場合、この総なめ作業はどんどん増えていく。

frameP で推論(10000個総なめ) → 
  結果Aをフレームとしてさらに推論(10000個総なめ)
    結果Cをフレームとしてさらに推論(10000個総なめ)
    ...
  結果Bをフレームとしてさらに推論(10000個総なめ)
    結果Dをフレームとしてさらに推論(10000個総なめ)
    ...

たとえば、結果が末端にいくまで10回推論を重ねる必要があって、推論の度に2個づつ結論ができる場合、2の9乗 * 10000 = 500万個 総なめしないといけない。

なので、この重い作業をなんとかしろという問題だと解釈する。

SICP 4.4.4.5 Ex. 4.78

  • 投稿日:
  • カテゴリ:

Ex. 4.78

amb評価器の上に query評価器を構築する。なんていう壮大な計画はとうぜん没。

amb評価器上で、検索プログラムを作るという感じで。(これでも結構大変)

一応、目標とする動作は

;;; Amb input
aaa = (amb THE-ASSERTIONS)
  (require (match? '(son Ada ?x) aaa))
  (print aaa)
;;; Amb output
(son Ada Jabal)
;;; Amb input
try-again
;;; Amb output
(son Ada Jubal)

こんな感じで。

関数 match? は、ambの組み込み関数にするか、amb上で定義するユーザ関数にするか、の2択だが、特にややこしくする理由もないので単純にユーザ関数にする。

amb評価器の本体の改造

(define primitive-procedures
  (list (list 'car car)
	...
	(list 'equal? equal?)                 ;; 追加
	(list 'string=? string=?)             ;; 追加
	(list 'symbol->string symbol->string) ;; 追加
	(list 'pair? pair?)                   ;; 追加
	(list 'substring substring)           ;; 追加
        ))

amb評価器上で定義するユーザ関数とグローバル変数

;;; Amb-Eval input:
(define THE-ASSERTIONS
   '((son Adam Cain)
     (son Cain Enoch)
     (son Enoch Irad)
     (son Irad Mehujael)
     (son Mehujael Methushael)
     (son Methushael Lamech)
     (wife Lamech Ada)
     (son Ada Jabal)
     (son Ada Jubal)))

;;; Amb-Eval input:
(define (var? symbol)
  (string=? "?" (substring (symbol->string symbol) 0 1)))

;;; Amb-Eval input:
;; フレームから変数の値探す。
;; 変数は ?x とか、フレームは ((?x . abc) (?y . 123)) とか。
(define (search-value x frame)  
  (if (null? frame)
      false
      (if (equal? x (car (car frame)))
	  (cdr (car frame))
	  (search-value x (cdr frame)))))

;;; Amb-Eval input:
;; パターンと表明のマッチを調べる。 
;; pattern は (son Ada ?x) とか、assertion は (son Ada Jubal)とか、
;; frameは((?x . abc) (?y . 123))とか
(define (match? pattern assertion frame)
  (cond ((equal? pattern assertion) frame) ;; パターンと表明が一致してればマッチ成功
	((pair? pattern) 
	 (if (pair? assertion)
	     (let ((ppp (car pattern)) ;; パターンの先頭と
		   (aaa (car assertion))) ;; 表明の先頭を比較する。
	       (if (var? ppp) ;; 変数の時
		   (let ((vvv (search-value ppp frame)))  ;; フレームから変数さがす
		     (if (eq? vvv false) ;; 変数なかったらフレームに追加してマッチ続行
			 (match? (cdr pattern) (cdr assertion) (cons (cons ppp aaa) frame))
			 (if (equal? aaa vvv) ;; 変数が既にあって、値が同じならマッチ続行
			     (match? (cdr pattern) (cdr assertion) frame)
			     false)))  ;; 変数が食い違っていたらマッチ失敗
		   (if (equal? aaa ppp)  ;; 変数でなくシンボルのとき。一致していたらマッチ続行。
		       (match? (cdr pattern) (cdr assertion) frame)
		       false)))  ;; 一致してなかったらマッチ失敗。
	     false))  ;; 他はマッチ失敗
	 (else false)))

;;; Amb-Eval input:
;;p.246
(define (an-element-of items)
  (require (not (null? items)))
  (amb (car items) (an-element-of (cdr items))))

テスト

;;; Amb-Eval input:
(let ((aaa (an-element-of THE-ASSERTIONS)))
  (require (match? '(son Ada ?x) aaa '()))
  aaa)
;;; Starting a new problem 
;;; Amb-Eval value:
(son Ada Jabal)
;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
(son Ada Jubal)
;;; Amb-Eval input:
try-again
;;; There are no more values of

amb が an-element-of になったりしてるが、だいたいOK。

次は、and を作ってみる。

やり方は2通り考えられる。

① requireを2つ並べると and の代わりにできる。

;;; Amb input
aaa = (amb THE-ASSERTIONS)))
bbb = (amb THE-ASSERTIONS)))
  (require (match? '(wife Lamech ?x) aaa))
  (require (match? '(son ?x ?y) bbb))
  (list aaa bbb)

② require 1つで対処。conjoin関数を追加。

;;; Amb input
aaa = (amb THE-ASSERTIONS)
bbb = (amb THE-ASSERTIONS)
  (require (conjoin (match? '(wife Lamech ?x) aaa)
                    (match? '(son ?x ?y) bbb)))
  (list aaa bbb)

なんかこんな感じで。どっちでもいいけど、① だと新しい関数を作らなくてもいいので ① でいく。

;;; Amb-Eval input:
(let ((aaa (an-element-of THE-ASSERTIONS))
      (bbb (an-element-of THE-ASSERTIONS)))
  (let ((frame (match? '(wife Lamech ?x) aaa '())))
    (require frame)
    (require (match? '(son ?x ?y) bbb frame)))
  (list aaa bbb))
;;; Starting a new problem 
;;; Amb-Eval value:
((wife Lamech Ada) (son Ada Jabal))
;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
((wife Lamech Ada) (son Ada Jubal))
;;; Amb-Eval input:
try-again
;;; There are no more values of

frameの引き渡しが若干面倒だが、だいたいOK。

次は not 。これも新しく negate関数つくらなくても、amb評価機の not を使えばOK。

;;; Amb-Eval input:
(let ((aaa (an-element-of THE-ASSERTIONS)))
  (require (not (match? '(son Ada ?x) aaa '())))
  aaa)
;;; Starting a new problem 
;;; Amb-Eval value:
(son Adam Cain)
;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
(son Cain Enoch)
...

次は or 、これも新しい関数(disjoin)なしの方向で。

A or B だが、amb評価器には or シンタックスが実装されていないので、if 文を利用する。

;;; Amb-Eval input:
(let ((aaa (an-element-of THE-ASSERTIONS)))
  (let ((frame (match? '(son Ada ?x) aaa '())))
    (if frame
	(require true)
	(require (match? '(son Adam ?x) aaa '())))
    aaa))
;;; Starting a new problem 
;;; Amb-Eval value:
(son Adam Cain)
;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
(son Ada Jabal)
;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
(son Ada Jubal)
;;; Amb-Eval input:
try-again
;;; There are no more values of

次は rule ですが、、、降参です。

一応、理由を列挙。

  • same関数くらいは単純に amb の define で作ればOKな気がする。
  • wheel関数は、関数の中でDB検索させる方法が?(ユーザ関数の中で amb を使っていい?そのときの動作は?)
  • son関数は、表明のsonと関数sonを混在させる方法が?
  • outranked-by関数は、無限ループする関数だが、どうやって無限ループさせるか?

まあ、こんな感じでもういいです。疲れました。

次に amb 上の query と query評価器の違いについて。上でみたようにずいぶん違う。私の作り方が悪いだけかもしれないが。

and の動作はまあまあ同じ。

not の動作は明らかに異なる。amb で作ると、(not B) が (All and (not B)) として出てくる。

or の動作もまあまあ同じ。表示のさせかたが違うだけ。

基本的に、amb 上の query は、query評価器に比べて frame をユーザが弄るので、異なる動作にすることは簡単だと思われる。なるべく同じような動作をさせることも可能だと思うが、どこまでも同じにできるかは?

SICP 4.4.4.5 Ex. 4.77

  • 投稿日:
  • カテゴリ:

Ex. 4.77

実際には、not は and 中でしか使われない。

(not B) とか (A or (not B)) なんていうのは、not がフィルターである限り無意味だからだ。

これを意味ある query にしたければ、(ALL and (not B)) とか (A or (ALL and (not B))) とするしかない。結局、not は and と組みで使うしかない。なので、この辺の対応は運用にまかせることとする。

ということで、not を遅延させるというのは、and すなわち conjoin の中で not の処理を後回しにするだけでよい。

こんな感じにする。

(and (not A)
     B
     C)

これを

(and B
     C
     (not A))

という風に処理する。

and が入れ子になった場合

(and A
     (and (not B)
          C
          D)
     E
     F)

これの not の遅延のさせ方は2つある。

①
(and A
     (and C
          D)
     E
     F
     (not B))
②
(and A
     (and C
          D
          (not B))
     E
     F)

フィルター not をなるべく遅延させるという意味では①がいいのだが、問題文の指示どおり、処理を速くするために②のほう採用する。このほうがコーディングも簡単。

ということで

(define (conjoin conjuncts frame-stream)
  (let ((ccc (reorder-conjuncts conjuncts)))
    (if (empty-conjunction? ccc)
	frame-stream
	(conjoin (rest-conjuncts ccc)
		 (qeval (first-conjunct ccc)
			frame-stream)))))

(define (reorder-conjuncts conjuncts)
  (let ((a '())
	(b '()))
    (define (grouping ccc)
      (if (not (empty-conjunction? ccc))
	  (let ((first (first-conjunct ccc)))
	    (if (eq? (type first) 'not)
	      (set! b (append b (cons first '())))
	      (set! a (append a (cons first '()))))
	    (grouping (rest-conjuncts ccc)))))
    (grouping conjuncts)
    (append a b)))

こんな感じ。まったく面白みのないソース。単に conjuncts の順番を入れ替えてから実行するようにしただけ。

一応動く。

;;; Query input:
(and (not (same Adam ?x)) (son ?x ?y))
;;; Query results:
(and (not (same Adam Ada)) (son Ada Jubal))
(and (not (same Adam Ada)) (son Ada Jabal))
(and (not (same Adam Methushael)) (son Methushael Lamech))
(and (not (same Adam Mehujael)) (son Mehujael Methushael))
(and (not (same Adam Irad)) (son Irad Mehujael))
(and (not (same Adam Enoch)) (son Enoch Irad))
(and (not (same Adam Cain)) (son Cain Enoch))
(and (not (same Adam Lamech)) (son Lamech Jubal))
(and (not (same Adam Lamech)) (son Lamech Jabal))

lisp-valueのほうも同じように and のなかで後ろ回しにすればよいと思われる。(ry

複雑な query や変な rule と絡めた場合はまったく考えていないけど、もういいです。

SICP 4.4.4.5 Ex. 4.76

  • 投稿日:
  • カテゴリ:

Ex. 4.76

SQLでいうところの、表結合での索引検索と全表検索のことである。

ふつうは、A and B の Aの結果とBの結果がともに少ないときは全表検索が有利である。Aの結果とBの結果がある程度多いときは索引検索が有利になる。しかし、queryシステムには索引がないので、例えば (?x . Adam) をキーに表を絞り込むということができない。結局全表検索になる。なので最初から全表検索&結合のほうが有利である。

方針: A and B について

  1. A のフレームストリームを取得する (a1 a2 a3 ...)
  2. B のフレームストリームを取得する (b1 b2 b3 ...)
  3. フレーム a1と(b1 b2 b3 ...) の各フレームを結合可能なら結合する。((a1 b1) (a1 b3) ...)とか
  4. フレーム a2と(b1 b2 b3 ...) の各フレームを結合可能なら結合する。((a2 b2) ...)とか
  5. ...
  6. 上の結合結果をまとめて結合する ( (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)))

SICP 4.4.4.5 Ex. 4.75

  • 投稿日:
  • カテゴリ:

Ex. 4.75

構文的には、not と同じ使い方。なので、negate を改造して作る。という方針で。あとは適当に。

;; 追加関数
(define (uniquely-asserted operands frame-stream)
  (stream-flatmap
   (lambda (frame)
     (let ((stream (qeval (negated-query operands)
			  (singleton-stream frame))))  ;; フレームの拡張結果を一旦ローカル変数へ
       (cond ((stream-null? stream) the-empty-stream)  ;; フレームなくなったとき。つまりマッチング失敗。
	     ((stream-null? (stream-cdr stream)) stream)  ;; フレーム拡張なし、ユニークであるとき
	     (else the-empty-stream)))) 	 ;;フレームが拡張して、ユニークでなくなった場合。
   frame-stream))

;; unique 形式の登録
(define (initialize-data-base rules-and-assertions)
...
  (put 'unique 'qeval uniquely-asserted)  ;; 追加
  (put 'and 'qeval conjoin)
...

フレームストリームの中のフレームの個数(0個、1個、2個以上)を判定するためどうしてもローカル変数を使わざるを得なかった。

動作テスト

;;; Query input:
(unique (job (Bitdiddle Ben) (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))))
------

SICP 4.4.4.5 Ex. 4.74

Ex. 4.74

a.

negate の例を考える。

(and (supervisor ?x (Warbucks Oliver)) (not (same ?x (Scrooge Eben))))

最初の (supervisor ?x (Warbucks Oliver)) で作られるフレームストリームは

[ {(?x . (Aull DeWitt))} {(?x . (Scrooge Eben))} {(?x . (Bitdiddle Ben))} ]

である。( )は変数束縛、{ } はフレーム、[ ] はフレームストリームとする。

次の (not ...) は negate 関数を呼び出す。

(negate '((same ?x (Scrooge Eben)))
        '[ {(?x . (Aull DeWitt))} {(?x . (Scrooge Eben))} {(?x . (Bitdiddle Ben))} ] ) 

negateの最初の引数には (not (same...))の cdr が渡されるので、((same ...)) とカッコが2重になっている。

negate の定義に simple-stream-flatmap を使った場合の動作

(negate '((same ?x (Scrooge Eben))) 
        '[ {(?x . (Aull DeWitt))} {(?x . (Scrooge Eben))} {(?x . (Bitdiddle Ben))} ] )
  ↓
(simple-stream-flatmap
 (lambda (frame)
   (if (stream-null? (qeval (negated-query '((same ?x (Scrooge Eben))))
                            (singleton-stream frame)))
       (singleton-stream frame)
       the-empty-stream))
 '[ {(?x . (Aull DeWitt))} {(?x . (Scrooge Eben))} {(?x . (Bitdiddle Ben))} ] ))
  ↓
(simple-flatten
 (stream-map 
   (lambda (frame)
     (if (stream-null? (qeval '(same ?x (Scrooge Eben))
                              (singleton-stream frame)))
         (singleton-stream frame)
         the-empty-stream))
 '[ {(?x . (Aull DeWitt))} {(?x . (Scrooge Eben))} {(?x . (Bitdiddle Ben))} ] )))
  ↓
map の適用。
 1個目のフレーム {(?x . (Aull DeWitt))} の結果は [ {(?x . (Aull DeWitt))} ]
 2個目のフレーム {(?x . (Scrooge Eben))} の結果は the-empty-stream
 3個目のフレーム {(?x . (Bitdiddle Ben))} の結果は [ {(?x . (Bitdiddle Ben))} ]
結局 map の結果は 
  [ [{(?x . (Aull DeWitt))}] the-empty-stream [{(?x . (Bitdiddle Ben))}] ] 
というストリームのストリームになる。
negateはストリームを増やすことはないので、
ストリームの中のストリームはシングルトンストリームまたは the-empty-stream のみである。
  ↓
(simple-flatten 
  `[ [{(?x . (Aull DeWitt))}] ,the-empty-stream [{(?x . (Bitdiddle Ben))}] ] )
  ↓
(stream-map <??>①
  (stream-filter <??>② 
                 `[ [{(?x . (Aull DeWitt))}] ,the-empty-stream [{(?x . (Bitdiddle Ben))}] ] ))

となる。( `はquasiquote , はunquote。実験するときは注意。)

ここまでくれば、<??>② は the-empty-stream を取り除くフィルターであり、<??>①はストリームの中のシングルトンストリームをフレームに下げる関数。要するに car であると分かる。

ということで答えは

(define (simple-flatten stream)
  (stream-map (lamda (x) (car x))
      	       (stream-filter (lambda (x) (not (eq? x the-empty-stream))) stream)))

b.

この問題の negate の場合

(negate '((same ?x (Scrooge Eben))) 
        '[ {(?x . (Aull DeWitt))} {(?x . (Scrooge Eben))} {(?x . (Bitdiddle Ben))} ] )
 ↓
[ {(?x . (Aull DeWitt))} . (delay {(?x . (Bitdiddle Ben))}) ] 

既存の negate の場合

(negate '((same ?x (Scrooge Eben))) 
        '[ {(?x . (Aull DeWitt))} {(?x . (Scrooge Eben))} {(?x . (Bitdiddle Ben))} ] )
 ↓
[ {(?x . (Aull DeWitt))} . (delay {(?x . (Bitdiddle Ben))}) ] 

となる。(flatten-streamは一見 the-empty-stream をフィルタしてないように見えるが、下位関数のinterleave-delyed が the-empty-stream を読み飛ばしている。)

ということで、結果のストリームはどちらも同じである。(当たり前か。結果変わってたら提案以前のバグだ。)

delayのタイミングについても、この問題の negate は入力ストリームの2個目以降の処理を遅延する。既存の negate も同じである。

ということで、他になんか?何もなければ、negate については、特に振る舞いは変わらないような気がする。

lisp-value, find-assertionsについては、、、疲れたので略。

SICP 4.4.4.5 Ex. 4.73

  • 投稿日:
  • カテゴリ:

Ex. 4.73

ここも前向きに考える。将来の修理のために、プログラムの下位層での無限ストリームを遅延させようとしているとする。

flatten-stream は、interleave を使って car と cdr を混ぜ合わせ、また cdr にだけ delay が入っている。

つまり、(flatten-stream (A B C)) は (A1 delay-B1 delay-C1 A2 delay-B2 delay-C2 ...)という感じのリストを返す。

このように、flatten-stream が遅延するので、これを呼び出す stream-flatmap も遅延し、さらにこれを呼び出す simple-query も遅延する。

simple-query について、Ex. 4.71 で入れた delay と Ex. 4.73(この問題)で入れた delay の役割分担はこんな感じ。

入力フレームストリーム
(flame1, flame2, ....無限)  ← 入力フレームストリームが無限 (Ex.4.73 (この問題)で対処)
  |        ↓
  ↓     flame2の拡張 (flame2-1, flame2-2, .. 無限) ← flame2の拡張が無限 (Ex.4.71で対処)
flame1の拡張 (flame1-1, flame1-2, ...無限)  ← flame1 の拡張が無限 (Ex.4.71で対処)

apply-rules について

apply-rules も stream-flatmap を呼び出している。

で、気づいたが、stream-flatmapはストリームの最初の car を遅延しない。なので、ストリームの最初が無限ループする rule である場合

apply-rules → apply-a-rule → qeval → apply-rules → apply-a-rule ...

という無限ループがまったく遅延されない。結局一番肝心な無限ループを起こす最下層部分が放置されたままである。

一体なんのために、Ex.4.71, 4.72, 4.73 と無限ストリームを一所懸命、遅延させてきたのか?

ほんとにくそプログラムだなあ。(-.-;)ボソ

SICP 4.4.4.5 Ex. 4.72

  • 投稿日:
  • カテゴリ:

Ex. 4.72

一応ヒントを確認する。

3.5.3節 interleave を使う理由 p202 → 2つの無限ストリームを、偏らずに交互に取り出すため。

なので、やはり前問と同じく、まっとうな答えは、無限ループをまともに扱えないシステムが、無限ストリームを考慮してコーディングしてもまったくもって意味がない。というものである。

一応、ここも前向きに考えて、将来の修理のために、無限ストリームを遅延させようとしていると考えよう。

この目的の場合 (append A (delay B)) だと、Bの無限ストリームは遅延できるが、Aの無限ストリームは遅延できない。ので不十分である。

一方 (interleave A (delay B)) だと、(A1 B1 A2 B2 ...) と交互に取り出すため、Bを遅延させるだけで、Aの無限ストリームも遅延させることができる。

なお、この目的のためなら (append (delay A) (delay B)) でもいい筈である。(Aばっかりとりだすというのは望ましくはないが、無限ストリームに対処するための修理という目的にはかなう筈)

(なお、無限ストリームは rule によってのみ発生する。表明は有限個しかない。)

SICP 4.4.4.5 Ex. 4.71

  • 投稿日:
  • カテゴリ:

Ex. 4.71

どう考えても、delay を使うメリットはない。

delay を使ったところで、ruleの無限ループを回避できるわけではない。結局、強制終了するしかない。

無限ループを起こす壊れたソフトが、途中経過を少しばっか印字したからといって許されるわけがない。(少なくとも私の感覚では。)

現状の query システムは、delay を入れていても rule の無限ループは止まらず、結局ユーザが運用で回避することになっている。(無限ループしないよう質問文を工夫する)

ということであれば、割り切って、4章のqueryシステム全体に散らばっているこの遅延関係のコードをすべてなくせばいい。ものすごくすっきりした易しいqueryシステムのソースになる。そして動作も現状とあんまり大差ない(筈)である。

一応、前向きに考えると、プログラムの下の層で delay を入れて無限ストリームの発覚を先送りにしておくと、無限ストリームへの対処を上の層で取り扱うことが可能になる。(例えば query-driver-loop で10個印字する度に、続行?のプロンプトを出すとか)。将来、このシステムを修理するときのために delay を入れていると考えられないこともない。

or についても同じ理由。A or B の B だけを delay させておけば、Aの無限ストリーム、Bの無限ストリームどちらの場合も、無限ストリームの発覚を先送りにできる。(A or B 内のストリーム結合にinterleave を使っているので)

SICP 4.4.4.5 Ex. 4.70

  • 投稿日:
  • カテゴリ:

Ex. 4.70

まず、THE-ASSERTIONSの中身は

THE-ASSERTIONS =
((son Adam Cain) . (delay ((son Cain Enoch) . (delay ((son Enoch Irad) ... )))

これの格納の様子を図にするとこんな感じ。 THE-ASSERTIONS

図のように、Schemeはリストを変数に代入するとき、リストのコピーはとらない。ポインタだけ格納する。promiseは delay で作られるオブジェクトである。(残念ながら、promiseオブジェクトの内部のデータの持ち方は不明。多分これもオブジェクトへのポインタを持つだけと思う。)

局所変数 old-assertion を使わずに set! するとどうなるか見てみよう。

(set! THE-ASSERTIONS (cons-stream '(son abc xyz) THE-ASSERTION))

THE-ASSERTIONS 2

ストリームの中のTHE-ASSERTIONSも新しい番地を指すだけである。前回のTHE-ASSERTIONSの番地を誰も覚えていない。この場合

(car THE-ASSERTIONS) → (son abc xyz)
(car (force (cdr THE-ASSERTIONS))) → (son abc xyz)
...

と、いくら cdr を繰り返しても、(son abc xyz) しか返ってこない。

局所変数 old-assertions を使った場合は

(let ((old-assetions THE-ASSERTIONS))
     (set! THE-ASSERTIONS (cons-stream '(son abc xyz) old-assertions)))

THE-ASSERTIONS 3

こんな感じになる。前回のTHE-ASSERTIONSの番地をold-assertions が覚えている。これだと

(car THE-ASSERTIONS) → (son abc xyz)
(car (force (cdr THE-ASSERTIONS))) → (son Adam Cain)
...

と、cdr で次々に表明を取り出せる。

ある言語の変数の保存方法は、こうでなければならないというロジカルな理由はない。若しくは、どんな格納の仕方でもロジカルに正しいと言える。CとかFORTRANの代入と違う動作だといって文句は言えない。

Scheme で cons-stream と set! の組み合わせが上の図のように変数を格納するのは、単に仕様である。

しかし、一見なんの意味もない、省略可能な中間変数にしか見えない old-assertions が実は必須だった。というのは罠以外の何者でもない。

この問題の意図は、cons-stream 使ったグローバル変数の set! には、こういう落とし穴があるので注意しましょう。ということである。

念のため、もし delay を使わないで、cons だけで THE-ASSERTIONS を作ったらどうなるであろうか?

この場合は、局所変数 old-assertions は要らない。CとかFORTRANの代入と同じように振る舞う。

(set! THE-ASSERTIONS (cons '(son abc xyz) THE-ASSERTION))

THE-ASSERTIONS 4

ということで、cons と cons-stream で set! の振る舞いが異なる。非常に間違い易い、、、だが、仕様である!我々は黙って注意するしかない。

(そもそも、THE-ASSERTIONSで cons-stream を使うメリットがないような気がする。consで十分ではないか?)

SICP 4.4.4 質問システムの実装

  • 投稿日:
  • カテゴリ:

amb評価器とうって変わって継続は一切でてこない。

ストリームの処理だけで実装している。あんまりややこしいところはない。

p286 4.4.4.5 データベースの保守ででてくる、get, put, assoc の定義がないように見えるが、 これは、3章 p159のものを使っている。注意。

DBの格納先は、2箇所ある。

①1つは THE-ASSERTIONS、THE-RULES というグローバル変数で、

②もう一つは、3章 p159 で出てくる make-table というクロージャの中の local-table という変数である。

(local-table から THE-ASSERTIONS、THE-RULES は完全に復元できるのに、わざわざ2重に持っている。理由はよく分からない。)

local-table は2次元のリストで第1次元のキーはsonとかjobとかで、第2次元のキーは assertion とか rule とかである。 table

こんな感じでDBが格納されている。

本文中 index と呼ばれているのは、表明や規則の名前のこと。 job とか son のことである。一意の番号とかではないので注意。

(get-indexed-assersions '(son ?x ?y))

とすると、第1次元のキー = son、第2次元のキー = assertion でDB検索して

((son Adam Cain) . (delay (son Cain Enoch) . (delay (son Irad Mehujael) . ... )))

というのが返ってくる。delay を省くと分かるが、単なる son 表明のリストである。

((son Adam Cain) (son Cain Enoch) (son Irad Mehujael) ... )

格納されたDBレコードに delay が付いているのは、store-assersion-in-index 関数の中で cons ではなく、cons-stream を使っているためである。

しかし、この delay はハッキリ言って無用である。

ストリームを遅延させる理由は、①ストリームが無限であるか、②未定義の変数をストリームに混ぜる場合であるが、DBの表明は有限個のレコードであり、未定義の変数も含まない。 なので遅延させる理由がない。(理由なく遅延させてもいいけど)

実際、store-assersion-in-index 関数の中で cons-stream を cons に置き換えても質問システムは動作する。

SICP 4.4.3 Ex. 4.69

  • 投稿日:
  • カテゴリ:

Ex. 4.69

greatがどんな動作をするか予想してみる。

入力
((great grandson) Adam ?)
出力
((great grandson) Adam Irad)
------
入力
((great great grandson) Adam ?)
出力
((great great grandson) Adam Methushael)

リストの最後が grandson であることをチェックする rule の動作の予想は

入力
(grandson-ended (great grandson))
出力
(grandson-ended (great grandson))
------
入力
(grandson-ended (son))
出力
なし

こんな感じか。

規則 great の変わっているところは、規則の名前がリストになっている点である。こんな規則が本当に作れるのか?作ったとして解釈系は実行できるのか?grandsonの前に great 以外が入っていないかチェックしなくていいのか?

気になる点は多いが、とりあえず、grandson-ended から作ってみる。

(rule (grandson-ended ?x)
      (append-to-form ?a (grandson) ?x))

こんな感じ。

次に greatの定義は

①((grandson) a) = (grandson a)
②X = (great grandson) or (great great grandson) or ... かつ (X a)= b 
  ならば  ((cons (great X)) a) = (son b) である。

こんな感じ。公理的定義というより、ほとんど帰納的定義になっている。 ①は帰納法の初期値の定義のようなもの。

rule形式にするために、②に中間変数を入れる。

②X = (great grandson) or (great great grandson) or ... かつ (X a)= b かつ (son b) = c
  ならば  ((cons (great X)) a) = c である。

ruleに置き換える

(rule ((grandson) ?a)
      (grandson ?a))

(rule ((great . ?X) ?a ?c)
      (and (son ?b ?c)
           (?X ?a ?b)
           (grandson-ended ?X)))

andの順番は工夫している。この順番以外、例えば grandson-ended を and の上のほうの行に置いたりすると無限ループしてしまう。

動かしてみる。

;;; Query input:
((great grandson) Adam ?)
;;; Query results:
((great grandson) Adam Irad)
------
;;; Query input:
((great great grandson) Adam ?)
;;; Query results:
((great great grandson) Adam Mehujael)
------
;;; Query input:
((great grandson) ?g ?ggs)
;;; Query results:
((great grandson) Irad Lamech)
((great grandson) Enoch Methushael)
((great grandson) Cain Mehujael)
((great grandson) Adam Irad)
((great grandson) Mehujael Jubal)
((great grandson) Mehujael Jabal)
------
;;; Query input:
(?relationship Adam Irad)
;;; Query results:
((great grandson) Adam Irad)

注意。最後の ?relationship の結果を得るには、Ex.4.64の無限ループする rule を DBから消しておくこと。これが残っていると、?relationship はこの rule もユニファイしようとして無限ループにはまる。(はまりました)

デバッグについて

Schemeはデバッグ機能が貧弱なので rule なんかのデバッグはとても困る。

仕方ないので apply-a-rule にデバッグプリントを入れてフレームと規則の本体を表示させている。あとは想像をたくましくするしかない。(それでも、ほとんど何が起こってるか分からない(泣)

(define (apply-a-rule rule query-pattern query-frame)
  (let ((clean-rule (rename-variables-in rule)))
    (let ((unify-result
           (unify-match query-pattern
                        (conclusion clean-rule)
                        query-frame)))
      (if (eq? #?=unify-result 'failed)
          the-empty-stream
          (qeval #?=(rule-body clean-rule)
                 (singleton-stream unify-result))))))

SICP 4.4.3 Ex. 4.68

  • 投稿日:
  • カテゴリ:

Ex. 4.68

まず簡単な手書き実験で reverse の性質を探ってみる。ヒントの append 云々を考慮して、、、

1. (reverse ()) = () とか
2. (reverse a) = a とか
3. (reverse (a)) = (a) とか
4. (reverse (a b) = (b a)とか
5. (reverse (a b c)) = (c b a)とか
6. (reverse (append (a b) (c))) = (append (c) (reverse (a b))) とか
7. (reverse (append (a b) (c d))) = (append (reverse (c d)) (reverse (a b))) とか
8. (reverse (cons a (b c))) = (append (reverse (b c)) (a)) とか
...

6.と7.と8.は、append の定義で使った cons と append の交換可能性に似ている。

また6.と7.と8.は、結局どれも同じ性質を表しているような気がする、、、ただ 7. は rule の実装のせいで無限ループしてしまう。

4.と5.は要素数をN個にしようとすると結局 6.、7.、8.になりそうな予感。

1.と2.と3.は初期値としてどれでもいいような、、、ただ空リストとか、リストでないものの reverse というのも意味がないかも。とまあこのへんは趣味。

ということで、3.と6.を採用して、reverse の公理的定義を作ってみる。

①単一要素のリスト(a)について、(reverse (a)) = (a) である。
②単一要素のリスト(c)と任意のリスト x について、
  (reverse (append x (c))) = (append (c) (reverse x))である。

②はこのままだと rule 形式にできないので、また中間変数を導入して言い換える。

②単一要素のリスト(c)と任意のリスト x, z, v, w について、
  (append x (c)) = z かつ (reverse x) = v かつ (append (c) v) = w
  ならば (reverse z) = w である。

この①と②をrule形式になおす。

①(rule (reverse (?a) (?a)))
②(rule (reverse ?z ?w)
        (and (append-to-form ?x (?c) ?z)
	      (reverse ?x ?v)
	      (append-to-form (?c) ?v ?w)))

実行してみる。順方向のreverse

;;; Query input:
(reverse (1 2 3) ?)
;;; Query results:
(reverse (1 2 3) (3 2 1))

ちゃんと動く。

逆方向のreverseは

;;; Query input:
(reverse ? (1 2 3))
.....無限ループ

残念ながら無限ループする。

この場合、全変数が未束縛状態で最初の append-to-form の再帰に突入して無限ループしているようだ。

(reverse (1 2 3) ?) も (reverse ? (1 2 3)) もどちらも無限ループしないような定義があればいいのだが、思いつかなかった。

append-to-form は cons を (a . b) と表現できるので、consを使った条件をruleの結論部分におくことができた。このおかけで順方向、逆方向ともに変数束縛できて、再帰が無限ループしないようになっていた。reverseではこんなラッキーな条件がない。append を rule の結論部に書ければいいがそうもいかない。というところで諦めた。

もっと全然違う切り口の定義、例えば2個の reverse の繰り返しとか、なにかあるかもしれない。

SICP 4.4.3 Ex. 4.67

  • 投稿日:
  • カテゴリ:

Ex. 4.67

規則の適用 → 規則の適用 → 規則の適用 → ...

というループである。実際の関数の再帰は、

qeval  → simple-query → apply-rules → apply-a-rule → qeval  → ...  

である。規則のユニファイや規則の本体を呼び出し実行しているのは、apply-a-rule なので、ここで履歴を検査してループ検知、および、履歴の記録をすればよい。

規則を実行する際、今実行しているのが、前回実行した規則と同じだったらループしていると分かる。

ループを発見したときの対処はユニファイ失敗と同じでよい。

履歴は大域環境に保存することにする。つまりグローバル変数とする。 (履歴を引数でたらいまわし、、、は面倒だったので諦めた。)

なお、単純ループのみ対象とする。規則→別の規則→元の規則というようなややこしいループは対象としない。なので履歴には前回実行した規則だけ保存すればよい。

こんな感じ

(define history 'dummy) ;; グローバル変数に履歴を追加

(define (query-driver-loop)
  (set! history 'dummy)  ;; 履歴を初期化
  (prompt-for-input input-prompt)
  ...(ry

(define (apply-a-rule rule query-pattern query-frame)
  (let ((clean-rule (rename-variables-in rule)))
    (let ((unify-result
	   (unify-match query-pattern
			(conclusion clean-rule)
			query-frame)))
      (if (eq? unify-result 'failed)
	  the-empty-stream
	  (if (eq? history (conclusion rule))  ;; 履歴チェック
	      the-empty-stream                    ;; ループを検知したら終了
	      (begin (set! history (conclusion rule)) ;; 履歴に今実行している規則をセット
		     (qeval (rule-body clean-rule)
			    (singleton-stream unify-result))))))))

太字が追加変更分。

履歴のチェックはユニファイのあとにおくこと。

ユニファイの前におくと、ユニファイしないルールまで履歴の対象になりまずい。(DBからルール探してるだけなのに履歴に残る)

動作はこんな感じ

;;; Query input:
(outranked-by2 (Bitdiddle Ben) ?who)
;;; Query results:
(outranked-by2 (Bitdiddle Ben) (Warbucks Oliver))

outranked-by2 は、Ex. 4.64 で無限ループしていたバージョンだが、ループ検知のおかげで、1回だけ実行して止まっている。

SICP 4.4.3 Ex. 4.66

  • 投稿日:
  • カテゴリ:

Ex. 4.66

sum は rule と似たような感じで動作する。

(sum ?amount
     (and (job ?x (computer programmer))
          (salary ?x ?amount)))

の動作を考える

query5

こんな感じでうまく動作する。

次に wheel の給料の合計を求めるために sum を変える。

(sum ?amount
     (and (wheel ?x )
          (salary ?x ?amount)))

これの動作を考えてみる。 query6

こんな感じ。

前の問い Ex. 4.65 で見たように Oliver が wheel に4回ヒットするので、wheelの給料の合計が

Benの給料+Oliverの給料 × 4 = 660000

になってしまう。欲しい合計は

 Benの給料+Oliverの給料 = 210000

なので、これはまずい。

打開案は、Oliverさんの重複を取り除くような仕組みがあればよい。例えば

(sum ?amount
     (and (wheel ?x )
          (distinct ?x)
          (salary ?x ?amount)))

みたいな感じで、「(distinct ?x) はフレームストリームから ?x の重複を取り除く。」というような機能があればよい。

SQLの select distinct ... と同じ機能。

SICP 4.4.3 Ex. 4.65

  • 投稿日:
  • カテゴリ:

Ex. 4.65

動作を追いかける。見やすくするため、人名、変数は短く書く。

パターン (wheel ?w)
規則の結論 (wheel ?p-1)

ユニファイしてできるフレーム

フレーム ((?w . ?p-1))

このフレームのもとで、規則の本体を作用させる

規則の本体
(and (supervisor ?m-1 ?p-1)  ・・・1. 
     (supervisor ?x-1 ?m-1)))  ・・・2.

1. とDBのパターンマッチでできるフレームストリーム

(
フレーム①((?w . ?p-1)(?m-1 . Alyssa)(?p-1 . Ben))
フレーム②((?w . ?p-1)(?m-1 . Cy)(?p-1 . Ben))
フレーム③((?w . ?p-1)(?m-1 . Lem)(?p-1 . Ben))
フレーム④((?w . ?p-1)(?m-1 . Louis)(?p-1 . Alyssa))
フレーム⑤((?w . ?p-1)(?m-1 . Ben)(?p-1 . Oliver))
フレーム⑥((?w . ?p-1)(?m-1 . Eben)(?p-1 . Oliver))
フレーム⑦((?w . ?p-1)(?m-1 . Robert)(?p-1 . Eben))
フレーム⑧((?w . ?p-1)(?m-1 . DeWitt)(?p-1 . Oliver)) 
)

最初のフレームがフレーム①〜フレーム⑧に拡張された。

次にANDなので、このフレームストリームを入力として 2. とDBのパターンマッチをする。

(
フレーム①((?w . ?p-1)(?m-1 . Alyssa)(?p-1 . Ben)(?x-1 . Louis))
フレーム②((?w . ?p-1)(?m-1 . Cy)(?p-1 . Ben) 失敗:?x-1にバインドできるものなし)
フレーム③((?w . ?p-1)(?m-1 . Lem)(?p-1 . Ben) 失敗:?x-1にバインドできるものなし)
フレーム④((?w . ?p-1)(?m-1 . Louis)(?p-1 . Alyssa) 失敗:?x-1にバインドできるものなし)
フレーム⑤-1((?w . ?p-1)(?m-1 . Ben)(?p-1 . Oliver)(?x-1 . Alyssa))
フレーム⑤-2((?w . ?p-1)(?m-1 . Ben)(?p-1 . Oliver)(?x-1 . Cy))
フレーム⑤-3((?w . ?p-1)(?m-1 . Ben)(?p-1 . Oliver)(?x-1 . Lem))
フレーム⑥((?w . ?p-1)(?m-1 . Eben)(?p-1 . Oliver)(?x-1 . Robert))
フレーム⑦((?w . ?p-1)(?m-1 . Robert)(?p-1 . Eben) 失敗:?x-1にバインドできるものなし)
フレーム⑧((?w . ?p-1)(?m-1 . DeWitt)(?p-1 . Oliver)失敗:?x-1にバインドできるものなし) 
)

フレーム⑤は拡張されてフレーム⑤-1、フレーム⑤-2、フレーム⑤-3の3つになった。

結局、パターンマッチが成功するのは

(
フレーム①((?w . ?p-1)(?m-1 . Alyssa)(?p-1 . Ben)(?x-1 . Louis))
フレーム⑤-1((?w . ?p-1)(?m-1 . Ben)(?p-1 . Oliver)(?x-1 . Alyssa))
フレーム⑤-2((?w . ?p-1)(?m-1 . Ben)(?p-1 . Oliver)(?x-1 . Cy))
フレーム⑤-3((?w . ?p-1)(?m-1 . Ben)(?p-1 . Oliver)(?x-1 . Lem))
フレーム⑥((?w . ?p-1)(?m-1 . Eben)(?p-1 . Oliver)(?x-1 . Robert))
)

の5つ。これらが印字される。なので ?w だけが印字されると、Oliverさんが無意味に4回もでてくるように見えるが、実際は、Oliverさんの陪臣が4人いるということを表している。

SICP 4.4.3 Ex. 4.64

  • 投稿日:
  • カテゴリ:

Ex. 4.64

動作を追いかける。見やすくするため、人名、変数は短く書く。

パターン  (outranked-by Ben ?w)
規則の結論  (rule (outranked-by ?s-1 ?b-1)  ← 規則の変数には連番がつく

ユニファイするとこんなフレームができる。

フレーム ((?s-1 . Ben) (?b-1 . ?w))

このフレームのもとで、規則を作用させる。

つまり、このフレームを入力として規則の本体で パターンマッチ or ユニファイを実行する。

規則の本体
(or (supervisor Ben ?b-1) ①
    (and (outranked-by ?m-1 ?b-1) ②
         (supervisor Ben ?m-1))) ③

規則の本体①とDBのパターンマッチでできるフレーム

フレーム① ((?s-1 . Ben) (?b-1 . ?w) (?b-1 . Oliver)) 

ここで、?w が具体的な値に束縛された解が1つみつかった。

規則の本体②とDBのパターンマッチは、また規則の適用になる。①とは OR なので別のフレームストリームになる。

パターン②  (outranked-by ?m-1 ?b-1)
規則の結論  (rule (outranked-by ?s-2 ?b-2))  ← 適用ごとに変数に新しい連番がつく

この規則の適用は、

1. outranked-by から自分自身 outranked-by を適用する再帰になっている。
2. さらに、パターンと規則に出てくる変数すべてが未束縛のままである。
   このままいくら再帰しても未束縛変数が束縛されない。

このような場合規則の再帰は無限ループになる。

無限ループについて

規則から自分規則への再帰的適用は基本的に無限ループになる。

たとえば

(append-to-form ?x ?y ?z)

と入力すると無限ループになる。

この無限ループを止める条件は

① 入力フレームの変数束縛によってユニファイが失敗すればよい。
   ユニファイが失敗すれば規則の本体は実行されない。つまりそのフレームのループは停止する。
   (フレームストリームの全フレームでユニファイ失敗すればループは完全に停止する。)

である。

たとえば

(append-to-form (1) (2) (3))

と最初からすべての変数が束縛された状態を入力するとループしない。(式の真偽はともかくループはしない)

本体にDB検索を持つ規則は、DBとのマッチングによって変数束縛すればユニファイ失敗してループを抜けることができる。

(outlanked-by ?x ?y)

は、自己再帰を含むが、?x, ?y がDBで束縛されることでループが止まる。

DB検索のない、ルールだけの規則(append-to-formなど)は、引数で変数束縛すればユニファイ失敗してループを抜けることができる。

(append-to-form ?x ?y (1 2 3))

は ?z の変数束縛が最終的に?x、?yの束縛を引き起こすのでループを抜けることができる。

(append-to-form ?x (3) ?z)

は ?y の変数束縛が ?x, ?z の束縛を引き起こせないので無限ループする。

(append-to-form (1) ?y ?z)

も、無限ループすべきだが、実際はエラーみたいな出力になる。バグかも?

なお

② 本体のない規則

は、そもそもループしない。

SICP 4.4.2 質問システム

  • 投稿日:
  • カテゴリ:

フレーム

驚いたことに"フレーム"の具体的説明が全然出てこない。p.290でやっと出てくる。ほとんど嫌がらせ。

フレームは連想リストである。こんなの↓
((?x . Adam)(?y . Cain))

?x は Adam に束縛されている。?y は Cain に束縛されている。という。

フレームストリームは、フレームのリストである。(連想リストのリスト)

フレームストリーム
(  ((?x . Adam)(?y . Cain))  ((?x . Cain)(?y . Enoch))  ((?x . Enoch)(?y . Irad))  )
   フレーム1                  フレーム2                   フレーム3

フレーム(連想リスト)も、ストリームも1列に並んでいる状態が一番値打ちがあるので、入れ子にしたりはしていないと思われる。

3章、4章前半の環境フレームとは別物なので注意。

環境フレーム
( (x y z) 1 2 3)

パターンマッチング

パターンマッチングの説明も、いまいち要領を得ないが具体的には多分このようなことと思われる。

query1

パターンマッチャは、入力のフレームと表明とパターンを見比べて出力のフレームを作る。

合成質問

以下の例を考える。ANDの例。

(and (job ?x (computer programmer))
     (address ?x ?y))

この合成質問の処理はこんな感じと思われる。

query2

フレーム1が最初のパターンマッチの後

((?x . (Hacker Alyssa P)))

だったのが、次のパターンマッチの後

((?x . (Hacker Alyssa P)) (?y . (Cambridge (Mass Ave) 78)))

となっている。?yの束縛が追加されている。これをフレームが拡張されたという。

(フレームに変数束縛を追加するのを拡張といい、また、ストリームにフレームを追加することも拡張という。)

2つ目のパターンマッチャがフレームを拡張するときの動作の詳細、、、まあ図から想像されるとおり。

ユニフィケーション

p267のlives-nearの例を考える。

(lives-near ?x (Bitdiddle Ben))

処理はこんな感じと思われる。

query3

ユニフィケーションは、パターンマッチの一種である。

パターンとDBの両方に ?変数 が入った場合のパターンマッチをユニフィケーションと呼んでいる。

ユニフィケーションとパターンマッチの処理の違いは以下のとおり。

パターンマッチの場合
パターン=(job ?x BBB) と DBの表明=(job AAA BBB) に対して
  → フレーム=((?x . AAA))

ユニフィケーションの場合
パターン=(lives-near ?x BBB) と DBの規則=(rule (lives-near ?p1 ?p2)) に対して
  → フレーム=((?x . ?p1) (?p2 . BBB))

パターンマッチは、パターン側の ?変数 だけフレームに入れる。
ユニフィケーションは、パターン側の ?変数 と DB側の ?変数 の両方をフレームに入れる。(違いっても、そんだけ)

rule内の変数は適用の度に連番がつけられる。(p283)

規則の本体の評価

ruleの結論部分でユニファイしたあと、ruleの本体部分を評価する。(p274)

append-to-formの動作

ついでに気になる append-to-form の動作も考えてみる。

query4

  1. パターン1は最初の引数が ( ) でないので規則2の結論部とはユニファイしない。
  2. パターン1は規則1の結論部とユニファイしてフレーム1を作る。unify1
    (なお、規則の変数には、適用ごとに連番がつく。)
  3. 続いて、規則1の本体が評価される。unify2
  4. 規則1の本体パターン2は、フレーム1で ?v-1 が ( ) に束縛されているので、規則1と本体パターン2のユニファイは失敗する。
    「→規則の本体評価→規則の本体が規則の場合さらに規則の本体評価→・・・」
    の繰り返しはここで止まる。
  5. 規則1の本体パターン2は、規則2の結論部とユニファイして、フレームストリーム2 をつくる。
  6. 規則2には本体がないので、本体の評価はしない。

フレームストリーム2を使ってパターン1を具体化する。

フレームストリーム2を見ると

?x = ?y-1 = ?y-3 = ?z-1 = (b) 

なのでパターン1は

(append-to-form (a) (b) (a b))

と印字される。

SICP 4.4.1 包含と推論の違い

  • 投稿日:
  • カテゴリ:

rule は推論を実行する。包含を実行するのではない。注意。

もし包含を実行するなら 「A(偽) ならば B(真)」と「A(偽) ならば B(偽)」 も出力しなければならない。が、これを出力してもまったく意味がない。

包含と推論の違いは以下のとおり

包含 「A ならば B」
推論 「((A ならば B) かつ A) のとき B を結論する」

包含は論理式。推論は論理式ではない。

推論とは 「A ならば B」の関係があって、さらに A が真のとき B が真と結論すること。

rule は定義に包含を使うが、rule が実行するのは推論のほう。

SICP 4.4.1 Ex. 4.63

  • 投稿日:
  • カテゴリ:

Ex. 4.63

(rule (grandson ?G ?S)
      (and (son ?F ?S)
	   (son ?G ?F)))

(rule (son ?M ?S)       ;;; ← rule の名前と DBのレコードの名前が重なっていてもうまく処理されるみたい
      (and (wife ?M ?W)
	   (son ?W ?S)))

実行する

;;; Query input:
(grandson Cain ?)
;;; Query results:
(grandson Cain Irad)

;;; Query input:
(grandson Methushael ?)
;;; Query results:
(grandson Methushael Jubal)
(grandson Methushael Jabal)

DBのレコードから見つかる son も、rule で見つかる son もどちらも探してくれる。

rule の名前と DBのレコードの名前が重なった場合、質問システムは 「rule の結果」 or 「DBのレコード検索の結果」を返してくれるようだ。(or は和集合ということ)