AAL sources (6 of 8)

Jonathan Amsterdam jba at wheaties.ai.mit.edu
Sun Jun 11 07:32:27 AEST 1989


;;; -*- Mode: LISP; Syntax: Common-lisp; Package: USER; Base: 10 -*-

;;; The deducer for AAL.
;;; Copyright 1988 by Jonathan Amsterdam.  All Rights Reserved.

(provide 'deducer)

(require 'initial "initial.lisp")
(require 'streams "streams.lisp")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Deductive retriever.

(defun assert (stmt)
  ;; Clobbers the Common-Lisp assert macro.
  ;; When a fact is asserted, it's translated into a b-rule to simplify the
  ;; rest of the deducer.
  (if (not (eq (car stmt) 'b-rule))
      (setq stmt `(b-rule ,stmt)))
  (when (add-to-database stmt)
    (report "~&Asserting ~a~%" stmt)
    (if (null (antecedent-of stmt))
	;; run rules only for facts
	(run-forward-rules *assertion-rules* (consequent-of stmt))))
    stmt)

(defun retract (stmt)
  (if (not (eq (car stmt) 'b-rule))
      (setq stmt `(b-rule ,stmt)))
  (when (remove-from-database stmt)
    (report "~&Retracting ~a~%" stmt)
    (if (null (antecedent-of stmt))
	;; run rules only for facts
	(run-forward-rules *retraction-rules* (consequent-of stmt))))
    stmt)

(defun run-forward-rules (rules fact)
  ;; Run a rule if its pattern matches the fact.
  (dolist (frule rules)
    (let ((bindings (unify fact (pattern-of frule) *globals*)))
      (when (not (eq bindings :fail))
	(report "~&Firing rule ~a~%" frule)
	(execute-action (action-of frule) bindings)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Deduce.

;;; A pattern is a list.  The following cars are special:

;;; (not <pattern>)
;;; (or <pattern>*)
;;; (and <pattern>*)
;;; (do <action>*)			the actions areis executed; always
;;; 					  succeeds
;;; any action				The action is executed; succeeds if its
;;; 					 result is non-NIL.  All free variables
;;; 					 in the action must be instantiated.

(defun deduce (pattern bindings)
  ;; Returns a stream of bindings (variable lists) for things that match the
  ;; pattern, or the empty stream if there are none. 
  (let ((func (get (car pattern) 'deduce)))
    (cond
      (func
       (funcall func pattern bindings))
      ((aal-action? (car pattern))
       (deduce-action pattern bindings))
      (t
       (deduce-pattern pattern bindings (find-possible-unifiers pattern))))))
  
(defunp (deduce nil) (pattern bindings)
  ;; The null pattern always succeeds.
  (declare (ignore pattern))
  (stream bindings))

(defunp (deduce not) (pattern bindings)
  ;; Pattern should be fully instantiated.  Returns a stream consisting of
  ;; bindings if the pattern is not satisfied, the empty stream if it is.
  (if (stream-empty? (deduce (second pattern) bindings))
      (stream bindings)
      *empty-stream*))

(defunp (deduce or) (pattern bindings)
  ;; Returns a stream of all bindings satisfying any pattern in the list.
  (stream-mapcan #'(lambda (p) (deduce p bindings)) 
		 (list->stream (cdr pattern))))

(defunp (deduce and) (pattern bindings)
  ;; Returns a stream of bindings (variable lists) for things that match all
  ;; the patterns, or the empty stream if there are none.
  (deduce-list (cdr pattern) bindings))

(defun deduce-list (pattern-list bindings)
  (if (null pattern-list)
      (stream bindings)
      (let ((bindings-stream (deduce (car pattern-list) bindings)))
	(stream-mapcan #'(lambda (b) (deduce-list (cdr pattern-list) b))
		       bindings-stream))))

(defunp (deduce do) (pattern bindings)
  ;; The action is executed and the result ignored.  Always succeeds.
  (execute-action (second pattern) bindings)
  (stream bindings))

(defun deduce-action (action bindings)
  ;; The action is executed and succeeds if the result is non-NIL.  It also
  ;; augments the bindings.
  (multiple-value-bind (result new-bindings)
      (execute-action action bindings)
    (if result
      (stream new-bindings)
      *empty-stream*)))

(defun deduce-pattern (pattern bindings possibilities)
  ;; This is the only place "real work" gets done.
  (if (null possibilities)
      *empty-stream*
      (let* ((rule (rename-rule (car possibilities)))
	     (bindings1 (unify pattern (consequent-of rule) bindings)))
	      (if (eq bindings1 :fail)
		  (deduce-pattern pattern bindings (cdr possibilities))
		  (stream-append
		    (deduce (antecedent-of rule) bindings1)
		    (deduce-pattern pattern bindings (cdr possibilities)))))))
  
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Unifier.
;;; This is a simplified unifier.  It doesn't do nested patterns, and it also
;;; doesn't do the "occur check".  See Abelson & Sussman for a full-blooded
;;; unifier.

(defun unify (pat1 pat2 bindings)
  ;;Returns :FAIL if it can't unify, a list of bindings if it can.
  (cond
    ((and (null pat1) (null pat2))
     bindings)
    ((or (null pat1) (null pat2))
     :fail)
    ((let* ((el1 (car pat1))
	    (el2 (car pat2))
	    (new-bindings (if (var? el1)
			      (unify-var el1 el2 bindings)
			      (unify-const el1 el2 bindings))))
       (if (eq new-bindings :fail)
	   :fail
	   (unify (cdr pat1) (cdr pat2) new-bindings))))))

(defun unify-var (v el bindings)
  (let ((val (var-value v bindings)))
    (if (eq val :unbound)
	(if (eq v '*)
	    ;; The * variable, like the underscore in Prolog, indicates a
	    ;; "don't care".  It matches, but we create no binding for it.
	    bindings
	    (add-binding v el bindings))
	(unify-const val el bindings))))

(defun unify-const (const el bindings)
  (if (var? el)
      (unify-var el const bindings)
      (if (eql const el) bindings :fail)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun find-possible-unifiers (pattern)
  (if (var? (car pattern))
      *db*
      (append  (get '* 'database)
	       (get (car pattern) 'database))))

(defun add-to-database (rule)
  ;; Returns NIL iff not added (because already present)
  (let ((index (index-of rule)))
    (cond
      ((member rule (get index 'database) :test #'equal)
       nil)
      (t
       (push rule (get index 'database))
       (push rule *db*)
       (pushnew index *indices*)
       t))))

(defun remove-from-database (rule)
  ;; Returns NIL iff not removed (because not present)
  (let* ((index (index-of rule))
	 (the-rule (car (member rule (get index 'database) :test #'equal))))
    (cond
      (the-rule
       (setf (get index 'database) (delete the-rule (get index 'database) :test #'eq))
       (setq *db* (delete the-rule *db* :test #'eq))
       t)
      (t nil))))

(defun index-of (rule)
  (car (consequent-of rule)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(defun var? (thing)
  ;; A variable is a lisp symbol that begins with a *, but does not end with
  ;; one (except for the single-character variable "*").  We institute this
  ;; last requirement so that lisp globals, traditionally written as *symbol*,
  ;; can be accessed from AAL.
  (if (symbolp thing) 
      (let* ((name (symbol-name thing))
	     (length (length name)))
	(and
	  (char= (char name 0) #\*)
	  (or (= length 1)
	      (not (char= (char name (1- length)) #\*)))))
      nil))

(defun add-binding (var value bindings)
  (cons (cons var value) bindings))

(defun var-value (var bindings)
  ;; Follow the chain of bindings to the end.
  (let ((val-pair (assoc var bindings)))
    (if (not val-pair)
	:unbound
	(let ((val (cdr val-pair)))
	  (if (var? val)
	      (var-value val bindings)
	      val)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Renaming variables in a rule.  
;;; This needs to be done so that variables with the same name from two
;;; different rules (or two instantiations of the same, recursive, rule) don't
;;; interact.

(defun rename-rule (rule)
  ;; Renames all the variables in rule.
  (copy-pattern rule nil))

(defun copy-pattern (pattern correspondences)
  ;; Copy pattern, renaming variables.  So that textually distinct occurrences
  ;; of the same variable are renamed the same way, we need to keep a list of
  ;; the old-var/new-var correspondences.  We first build up an a-list of
  ;; the correspondences, then let sublis do the work.
  (let ((new-correspondences (create-correspondences pattern correspondences)))
    (if new-correspondences
	(sublis new-correspondences pattern)
	;; nothing to substitute (i.e. pat has no variables) so no need to copy
	pattern)))

(defun create-correspondences (pattern correspondences)
  ;; Avoid renaming global and local variables.
  (cond
    ((null pattern)
     correspondences)
    ((atom pattern)
     (if (and (var? pattern) 
	      (not (assoc pattern correspondences))
	      (not (assoc pattern (or *protected-vars* *globals*))))
	 (add-binding pattern (rename-var pattern) correspondences)
	 correspondences))
    (t
     (create-correspondences (cdr pattern)
			     (create-correspondences (car pattern)
						     correspondences)))))


(defun rename-var (var)
  ;; Generate a new symbol.
  (gentemp (symbol-name var)))

  
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; For testing.

(defun dedp (p)
  ;; for testing only
  (let ((uvars (reverse (unbound-vars-in-pattern p *globals* nil))))
    (mapcar #'(lambda (b) (extract-bindings b uvars))
	    (stream->list (deduce p *globals*)))))

(defun extract-bindings (bindings var-names)
  (mapcar #'(lambda (name) (cons name (var-value name bindings)))
	  var-names))

(defun unbound-vars-in-pattern (pattern bindings unbound-vars)
  (cond
    ((null pattern)
     unbound-vars)
    ((atom pattern)
     (if (and (var? pattern) (unbound? pattern bindings))
	 (adjoin pattern unbound-vars)
	 unbound-vars))
    (t
     (unbound-vars-in-pattern
       (cdr pattern) bindings
       (unbound-vars-in-pattern (car pattern) 
				bindings unbound-vars)))))

(defun unbound? (var bindings)
  (eq (var-value var bindings) :unbound))

(defun asserts (list)
  (dolist (pat list)
    (assert pat)))

(defun show-db (&optional predicate)
  (fresh-line)
  (dolist (stmt (if predicate (get predicate 'database) *db*))
    (format t "~s~%" (if (null (antecedent-of stmt))
			 (consequent-of stmt)
			 stmt))))

;;; End deducer.lisp.



More information about the Alt.sources mailing list