JESS: Rule LHS patterns and multiple hierarchical object models.

View: New views
5 Messages — Rating Filter:   Alert me  

JESS: Rule LHS patterns and multiple hierarchical object models.

by Nguyen, Son :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Rule LHS patterns and multiple hierarchical object models.

Hi,

My goal is to have logical expressions in rule LHS involving multiple templates derived from hierarchical Java models:


public class PO {
        public Customer customer;
        // setter, getter not shown.
}

public class Customer {
        public String name;
        public Address address;
        // setters, getters not shown.
}

public class Address {
        public int streetNumber;
        public String street;
        public String city;
        // setters, getters not shown.
}

Templates:

(deftemplate Customer
    (declare (from-class Customer)
                 (include-variables TRUE)))

(deftemplate PO
    (declare (from-class PO)
                 (include-variables TRUE)))


The following rule can handle an expression such as:
If (purchaseOrder.customer.address.street == customer.address.street)


(defrule rule
    (Customer (address ?address))
    (PO (customer ?customer))
    (test (= 0 (str-compare ((?customer getAddress) getStreet) (?address getStreet)))) 
    =>
    (bind ?name (?customer getName))
    (bind ?addr (?address getStreet))
    (printout t ?addr ": customer: " ?name crlf)
)

Are there better, more elegant ways to accomplish the same?

I tried:

?po <-  (PO (customer ?customer))
?customer <-  (Customer (address ?address))

(test (=  (((?po getCustomer) getAddress) getStreet)  ((?customer getAddress) getStreet)))

But ?po and ?customer are facts (Jess.Fact) that have no getCustomer and getAddress methods.


Son


Re: JESS: Rule LHS patterns and multiple hierarchical object models.

by Michael Smith-30 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


(defrule rule
    (Customer (address ?address))
    (PO (customer ?customer))
    (test (= 0 (str-compare ((?customer getAddress) getStreet) (?address getStreet)))) 
    =>
    (bind ?name (?customer getName))
    (bind ?addr (?address getStreet))
    (printout t ?addr ": customer: " ?name crlf)
)

Are there better, more elegant ways to accomplish the same?

Beauty is in the eye of the beholder, of course, but I'll offer one alternative that might work and then a little commentary.

I tried:

?po <-  (PO (customer ?customer))
?customer <-  (Customer (address ?address))

(test (=  (((?po getCustomer) getAddress) getStreet)  ((?customer getAddress) getStreet)))

But ?po and ?customer are facts (Jess.Fact) that have no getCustomer and getAddress methods.


(defrule match-street-in-purchase-to-other-customers-for-some-reason
(PO (customer ?poCustomer))
(Customer (OBJECT ?poCustomer)  (address ?poAddress))
(Address (OBJECT ?poAddress) (street ?street))
(Customer (OBJECT ?customer&~?poCustomer) (address ?customerAddress)) ;This assumes we aren't matching the PO customer
(Address (OBJECT ?customerAddress) (street ?street)
=>
;; do something here with something in purchase order unshown property (other than customer)?
)

It would also be possible to write methods on the classes that assist in this form of matching, to reduce the direct navigation through the object relationships.  Something like a streetEq method, or more directly a getCustomerStreet() method on the PO class to avoid navigating in the pattern matches across the Customer.

There cam be real tension between object-oriented programming and rule-based programming.  Encapsulation of objects and avoiding breaking Demeter's law can take a lot of the classic OPS5 rule look out of rule-programming.  Adding properties (and accessors) to all the slots one might navigate through can add a burden to adding objects to the engine as the properties are shadowed, particularly if the properties are not easily computed/virtual.

- Mike



Re: JESS: Rule LHS patterns and multiple hierarchical object models.

by Socrates Frangis :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

I agree with the same, that adding a method to your Java class would make the comparisons a lot easier. One thing you might not have noticed or considered was that your getters/setters are not neccesary in the way you designed your Java beans. Since your class members are public you can do a direct comparison between the PO.customer.address & Customer.address Strings. So as your code stands, it would suffice to just directly access them.

For more of a proper "coding style" when using Java beans, make those class members private, keep the getters/setters, and @Override your .equals method in the class. By default it will accept Object obj and you can code in what fields you want to use for a comparison. Since you know that customer and PO both have the same address string inside of them, you can code the .equals method to just compare those strings and it would make the comparison method you call in your 'test' line a lot simpler; maybe something along the lines of

(defrule rule
    ?c <- (Customer (address ?address))
    ?p <- (PO (customer ?customer))
    (test (?p (equals ?c)))

I could be wrong...might this should work with proper kung-fu in your Java classes.  


On Fri, Sep 4, 2009 at 7:24 PM, Michael Smith <michaeljsmith@...> wrote:

(defrule rule
    (Customer (address ?address))
    (PO (customer ?customer))
    (test (= 0 (str-compare ((?customer getAddress) getStreet) (?address getStreet)))) 
    =>
    (bind ?name (?customer getName))
    (bind ?addr (?address getStreet))
    (printout t ?addr ": customer: " ?name crlf)
)

Are there better, more elegant ways to accomplish the same?

Beauty is in the eye of the beholder, of course, but I'll offer one alternative that might work and then a little commentary.

I tried:

?po <-  (PO (customer ?customer))
?customer <-  (Customer (address ?address))

(test (=  (((?po getCustomer) getAddress) getStreet)  ((?customer getAddress) getStreet)))

But ?po and ?customer are facts (Jess.Fact) that have no getCustomer and getAddress methods.


(defrule match-street-in-purchase-to-other-customers-for-some-reason
(PO (customer ?poCustomer))
(Customer (OBJECT ?poCustomer)  (address ?poAddress))
(Address (OBJECT ?poAddress) (street ?street))
(Customer (OBJECT ?customer&~?poCustomer) (address ?customerAddress)) ;This assumes we aren't matching the PO customer
(Address (OBJECT ?customerAddress) (street ?street)
=>
;; do something here with something in purchase order unshown property (other than customer)?
)

It would also be possible to write methods on the classes that assist in this form of matching, to reduce the direct navigation through the object relationships.  Something like a streetEq method, or more directly a getCustomerStreet() method on the PO class to avoid navigating in the pattern matches across the Customer.

There cam be real tension between object-oriented programming and rule-based programming.  Encapsulation of objects and avoiding breaking Demeter's law can take a lot of the classic OPS5 rule look out of rule-programming.  Adding properties (and accessors) to all the slots one might navigate through can add a burden to adding objects to the engine as the properties are shadowed, particularly if the properties are not easily computed/virtual.

- Mike




Parent Message unknown Re: JESS: Rule LHS patterns and multiple hierarchical object models.

by Nguyen, Son :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Re: JESS: Rule LHS patterns and multiple hierarchical object models.

Thanks, Mike, for offering other solutions.

It is always refreshing to see different solutions to the same problem

As to the idea of writing methods to assist, it is not a viable solution for me because my solution has to be dynamic as the end users can pick and choose whatever fields of a data model and compare to another data model. The rules are generated based on the users inputs.

I had also experimented with writing Jess functions to accomplish the same.

Son


Re: JESS: Rule LHS patterns and multiple hierarchical object models.

by Wolfgang Laun-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Attributes of contained objects may be made attributes of the containing object by adding getters to the latter that propagate to the contained object. I think that the extra effort for implementing these in the (shadow) fact classes pays back when writing rules that require such accesses.

Of course, going down three (pr more) levels as in PO.getCustomer().getAddress().getStreet() might bloat these classes considerably or even intolerably. In such cases, consider inserting a contained top level object as a fact of its own.

-W


On Sat, Sep 5, 2009 at 4:24 AM, Michael Smith <michaeljsmith@...> wrote:

(defrule rule
    (Customer (address ?address))
    (PO (customer ?customer))
    (test (= 0 (str-compare ((?customer getAddress) getStreet) (?address getStreet)))) 
    =>
    (bind ?name (?customer getName))
    (bind ?addr (?address getStreet))
    (printout t ?addr ": customer: " ?name crlf)
)

Are there better, more elegant ways to accomplish the same?

Beauty is in the eye of the beholder, of course, but I'll offer one alternative that might work and then a little commentary.

I tried:

?po <-  (PO (customer ?customer))
?customer <-  (Customer (address ?address))

(test (=  (((?po getCustomer) getAddress) getStreet)  ((?customer getAddress) getStreet)))

But ?po and ?customer are facts (Jess.Fact) that have no getCustomer and getAddress methods.


(defrule match-street-in-purchase-to-other-customers-for-some-reason
(PO (customer ?poCustomer))
(Customer (OBJECT ?poCustomer)  (address ?poAddress))
(Address (OBJECT ?poAddress) (street ?street))
(Customer (OBJECT ?customer&~?poCustomer) (address ?customerAddress)) ;This assumes we aren't matching the PO customer
(Address (OBJECT ?customerAddress) (street ?street)
=>
;; do something here with something in purchase order unshown property (other than customer)?
)

It would also be possible to write methods on the classes that assist in this form of matching, to reduce the direct navigation through the object relationships.  Something like a streetEq method, or more directly a getCustomerStreet() method on the PO class to avoid navigating in the pattern matches across the Customer.

There cam be real tension between object-oriented programming and rule-based programming.  Encapsulation of objects and avoiding breaking Demeter's law can take a lot of the classic OPS5 rule look out of rule-programming.  Adding properties (and accessors) to all the slots one might navigate through can add a burden to adding objects to the engine as the properties are shadowed, particularly if the properties are not easily computed/virtual.

- Mike