;;;; ルールベースの表現

(define *rule-base*
  '((rule1 (and (USA) (English)) --> (Honolulu))
    (rule2 (and (Europe) (France)) --> (Paris))
    (rule3 (and (USA) (Continent)) --> (LosAngels))
    (rule4 (and (Island) (Equator)) --> (Honolulu))
    (rule5 (and (Asia) (Equator)) --> (Singapore))
    (rule6 (and (Island) (Micronesia)) --> (Guam))
    (rule7 (Swimming) --> (Equator))))

;;;; ワーキングメモリーの表現

(define *working-memory* '((Honolulu)))

;;;; 推論エンジンの実現

;; Step1 照合
;; 目標をルールの結論部として含むルールの集まりを探す。その
;; ようなルールがなければ、仮定は否定されたとして終了する。
;; Step2 競合の解消
;; ルールの集まりの中からルールを1つ選択する。前向き推論と
;; 同様に、ユーザーがこれを行うものとする。
;; Step3 動作
;; 選択されたルールの条件部への各要素がつぎの条件のいずれかを
;; 満たしていれば、これらを新しい目標値とし、Step1 へと戻る。
;; 1) 他のルールへの結論部として含まれている。
;; 2) ユーザーへの問い合わせによって肯定された。
;; これらの条件が満足されなければ、仮定は否定されたこととし
;; て終了する。

(define (backward-reasoning memory)	;目標を memory とする
  ;; Step-1 最初の目標の照合と Step-2 競合解消
  (let loop ((rule (choice (pattern-matching-back memory)))
	     (memory memory))
    ;; 実行可能なルールがなければ終了
    ;; quit が入力されたら終了
    (if (or (null? rule) (eq? rule 'quit))
	'end
	(let ((memory (rule-action-back rule))) ; Step-3 動作
	  (output-data-back memory)		; 結果の出力
	  (loop (choice (pattern-matching-back memory)) memory)))))

;;; 仮定が成立しないときに、そのことを出力する手続き
(define (output-data-back item)
  (and (null? item)
       (printn (caar *working-memory*)
	       " is unsuitable for you.")))

;; すべての引数を印字したのち改行する手続き
(define (printn . x)
  (for-each display x)
  (newline))

;;;; 競合解消 : choice

;; 選択されたルールのルール名を評価値とする
(define (choice lst)			;lst は実行可能なルールの集まり
  (cond ((null? lst) '())
	(else
	 (printn "enable rules : " lst)
	 (display "enter rule-name >> ")
	 (read))))			;ルール名の読み込み

;;;; 照合 : pattern-matching-back

;;; states で表される結論部をもつルールを
;;; *rule-base* から探す手続き
(define (pattern-matching-back states)
  ;; 全体が評価値
  (map get-rulename			;以降が真ならそのルールを含める
       (filter (lambda (candidate)	;対象とするルール
		 (rule-cond? (get-action candidate) states))
	       *rule-base*)))

;; プロダクションルールの条件部 conds がワーキ
;; ングメモリー states に含まれているかどうかを
;; 調べる手続き
(define (rule-cond? conds states)
  (or (null? conds)
      (if (eq? (car conds) 'and)	;論理積であるか?
	  (condition-aux? (cdr conds) states)
	  (member conds states))))	;単独の場合

(define (condition-aux? conds states)	;論理積の場合
  (or (null? conds)
      (and (member (car conds) states)
	   (condition-aux? (cdr conds) states))))

;;;; プロダクションルールの実現

(define (get-rulename rule) (car rule))
(define (get-cond rule) (cadr rule))
(define (get-action rule) (fourth rule))

;;;; 実行 : rule-action-back

;;; 選択されたルールの条件部が論理積の形式である
;;; かどうかに応じて、 eval-action-back を呼び出し、
;;; 新しい目標とすべき要素のリストを返す
(define (rule-action-back r)		; r は選択されたルール名
  (let ((rule (get-rule r *rule-base*))) ;ルール r を取り出す
    (if (null? rule)
	'()
	(let ((items (get-cond rule)))
	  (let ((item (car items)))
	    (eval-action-back
	     (if (eq? item 'and)	;論理積であるか?
		 (cdr items)		;論理積の場合
		 `(,items))
	     '()))))))

(define (eval-action-back items lst)
  (if (null? items)
      lst
      (let ((item (car items)))
	(let ((rules (pattern-matching-back `(,item))))
	  (call/cc
	   (lambda (k)
	     (eval-action-back (cdr items)
			       ;; 条件部が他のルールの結論部に含まれている場合
			       (if (or (pair? rules)
				       ;; あるいはユーザーに質問
				       (eq? (question item) 'y))
				   ;; 論理積で記述されているならばつぎの条件の評価
				   (cons item lst)
				   (k '())))))))))

(define (question lst)
  (and (pair? lst)
       (begin
	 (printn "Do you feel satisfied next condition ?")
	 (printn (car lst))		;条件の表示
	 (display "Please input 'y' or 'n' >> ")
	 (read))))			; y, n の読み込み

;; ルール集合 rules の中のルール名 r の内容を評価値とする手
;; 続き
(define (get-rule r rules)		; rules はルール集合
  (if (null? rules)
      '()				; rules はルールベース
      (let ((rule (car rules)))
	(if (eq? (car rule) r)		;ルール名のチェック
	    rule			;選択されたルール
	    (get-rule r (cdr rules))))))

;; ;; 実行例
;; > (backward-reasoning *working-memory*)
;; enable rules : (rule1 rule4)
;; enter rule-name >> rule4
;; Do you feel satisfied next condition ?
;; Island
;; Please input 'y' or 'n' >> y
;; enable rules : (rule7)
;; enter rule-name >> rule7
;; Do you feel satisfied next condition ?
;; Swimming
;; Please input 'y' or 'n' >> y
;; end
;; >