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 をユーザが弄るので、異なる動作にすることは簡単だと思われる。なるべく同じような動作をさせることも可能だと思うが、どこまでも同じにできるかは?