Lisp HUG Maillist Archive

KW issue: doing hash-table queries from TEST predicates

Greetings. I'd like clarification on a KW issue we're having. The doc says:

"Lisp tests, and any functions invoked by them, should not depend on any
dynamic global data structures, as changing such structures (and hence the
instantiations of the rule) will be invisible to the inference engine."

I read this as saying I should not write a rule like this, where *COUNTER*
can be modified outside of the inference engine:

(defvar *counter* 0)

(defrule busted-rule
  :forward
  (some-fact ? count ?count)
  (test (> ?count *counter*))
  -->
  ...)

Makes sense to me. However, what about hash table lookups?

(defvar *table* (make-hash-table))

(defun key-present-p (key)
  (gethash key *table*))

(defrule frodo
  :forward
  (some-fact ?fact key-value ?value)
  (test (not key-present-p ?value))
  -->
  ((add-this-fact-to-table ?fact ?value)))

To me, this should work just fine.

If the latter scenario is allowed, then we've probably found a bug in KW. If
it isn't, then I don't understand why it isn't. And if it isn't, what is the
proper way to implement scenarios like this?

Thanks and Regards,

David E. Young
Bloodhound Software, Inc.
http://bloodhoundinc.com

"For wisdom is more precious than rubies,
and nothing you desire can compare with her."
  -- Prov. 8:11

"But all the world understands my language."
  -- Franz Joseph Haydn (1732-1809)


This email message is for the sole use of the intended recipients(s) and may contain confidential and privileged information of Bloodhound Software, Inc.. Any unauthorized review, use, disclosure is prohibited. If you are not the intended recipient, please contact the sender by reply email and destroy all copies of the original message.


Re: KW issue: doing hash-table queries from TEST predicates

Young, David wrote:

>Greetings. I'd like clarification on a KW issue we're having. The doc says:
>
>"Lisp tests, and any functions invoked by them, should not depend on any
>dynamic global data structures, as changing such structures (and hence the
>instantiations of the rule) will be invisible to the inference engine."
>
>I read this as saying I should not write a rule like this, where *COUNTER*
>can be modified outside of the inference engine:
>
>(defvar *counter* 0)
>
>(defrule busted-rule
>  :forward
>  (some-fact ? count ?count)
>  (test (> ?count *counter*))
>  -->
>  ...)
>
>Makes sense to me. However, what about hash table lookups?
>
>(defvar *table* (make-hash-table))
>
>(defun key-present-p (key)
>  (gethash key *table*))
>
>(defrule frodo
>  :forward
>  (some-fact ?fact key-value ?value)
>  (test (not key-present-p ?value))
>  -->
>  ((add-this-fact-to-table ?fact ?value)))
>
>To me, this should work just fine.
>
>If the latter scenario is allowed, then we've probably found a bug in KW. If
>it isn't, then I don't understand why it isn't. And if it isn't, what is the
>proper way to implement scenarios like this?
>
>  
>
Hi,

If you modify the hash table during execution then you have invalidated 
the conditions under which the rule engine works. If the hash table 
contents are fixed by the time you start the rule engine then everything 
should be okay.

 From the example code fragment above (I'm guessing some parens are 
missing from around the key-present-p call) it looks as if you are 
modifiying the table. This is not-allowed.

The reason that it is not allowed is that the LHS tests are compiled 
into a RETE network which always maintains a complete idea of which 
conditions are satisfied and therefore which rules are eligible to fire. 
When a rule fires it generally either creates a new managed object or 
modifies the slot of a managed object. The RETE network uses this 
knowledge to incrementally and efficiently determine which conditions 
are now satisfied and which rules are next eligible to fire. By modifing 
effectively unmanaged state you are breaking the assumptions of the RETE 
network.

The simplest transformation that will let you do what you want to do is 
to move the test out of the static LHS of the rule and into the dynamic 
RHS of the rule:

(defrule frodo
  :forward
  (some-fact ?fact key-value ?value)
  -->
  ((test (not (key-present-p ?value))))
  ((add-this-fact-to-table ?fact ?value)))

The RHS does not have the restriction that you quote. The RHS is not 
part of the RETE network and so any tests can be done there and they are 
done in a completely normal way. BTW this is untested code. Your number 
of parens may vary... :-j

In fact you could transform it further by combining the two parts of the 
RHS:

(defrule frodo
  :forward
  (some-fact ?fact key-value ?value)
  -->
  ((add-this-fact-to-table-if-not-there ?fact ?value)))

Furthermore, if you know the table does not contain any of the current 
facts by the time the inference engine is started then you can omit the 
check since KW will not fire a rule twice for the same fact unless you 
set up a rule context that allows that:

(defrule frodo
  :forward
  (some-fact ?fact key-value ?value)
  -->
  ((add-this-fact-to-table ?fact ?value)))

HTH

__Jason




Updated at: 2020-12-10 08:55 UTC