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回だけ実行して止まっている。