next up previous contents
Next: Finding Assertions by Pattern Up: Implementing the Query System Previous: The Driver Loop and

   
The Evaluator

The qeval procedure, called by the query-driver-loop, is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special forms by a data-directed dispatch using get and put, just as we did in implementing generic operations in chapter 2. Any query that is not identified as a special form is assumed to be a simple query, to be processed by simple-query.

(define (qeval query frame-stream)
  (let ((qproc (get (type query) 'qeval)))
    (if qproc
        (qproc (contents query) frame-stream)
        (simple-query query frame-stream))))
Type and contents, defined in section [*], implement the abstract syntax of the special forms.

Simple queries

The simple-query procedure handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query.

(define (simple-query query-pattern frame-stream)
  (stream-flatmap
   (lambda (frame)
     (stream-append-delayed
      (find-assertions query-pattern frame)
      (delay (apply-rules query-pattern frame))))
   frame-stream))

For each frame in the input stream, we use find-assertions (section [*]) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules (section [*]) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using streamappenddelayed, section [*]) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise [*]). The streams for the individual input frames are combined using stream-flatmap (section [*]) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern.

Compound queries

And queries are handled as illustrated in figure [*] by the conjoin procedure. Conjoin takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries.

(define (conjoin conjuncts frame-stream)
  (if (empty-conjunction? conjuncts)
      frame-stream
      (conjoin (rest-conjuncts conjuncts)
               (qeval (first-conjunct conjuncts)
                      frame-stream))))
The expression

(put 'and 'qeval conjoin)
sets up qeval to dispatch to conjoin when an and form is encountered.

Or queries are handled similarly, as shown in figure [*]. The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed procedure from section [*]. (See exercises [*] and [*].)

(define (disjoin disjuncts frame-stream)
  (if (empty-disjunction? disjuncts)
      the-empty-stream
      (interleave-delayed
       (qeval (first-disjunct disjuncts) frame-stream)
       (delay (disjoin (rest-disjuncts disjuncts)
                       frame-stream)))))

(put 'or 'qeval disjoin)
The predicates and selectors for the syntax of conjuncts and disjuncts are given in section [*].

Filters

Not is handled by the method outlined in section [*]. We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended.

(define (negate operands frame-stream)
  (stream-flatmap
   (lambda (frame)
     (if (stream-null? (qeval (negated-query operands)
                              (singleton-stream frame)))
         (singleton-stream frame)
         the-empty-stream))
   frame-stream))

(put 'not 'qeval negate)

Lisp-value is a filter similar to not. Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables.

(define (lisp-value call frame-stream)
  (stream-flatmap
   (lambda (frame)
     (if (execute
          (instantiate
           call
           frame
           (lambda (v f)
             (error "Unknown pat var - LISP-VALUE" v))))
         (singleton-stream frame)
         the-empty-stream))
   frame-stream))

(put 'lisp-value 'qeval lisp-value)

Execute, which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system.

(define (execute exp)
  (apply (eval (predicate exp) user-initial-environment)
         (args exp)))

The always-true special form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section [*]) to provide bodies for rules that were defined without bodies (that is, rules whose conclusions are always satisfied).

(define (always-true ignore frame-stream) frame-stream)

(put 'always-true 'qeval always-true)
The selectors that define the syntax of not and lisp-value are given in section [*].


next up previous contents
Next: Finding Assertions by Pattern Up: Implementing the Query System Previous: The Driver Loop and
Ryan Bender
2000-04-17