[rules-users] Maps in Drools

André Thieme address.good.until.2009.dec.14 at justmail.de
Wed Aug 19 18:07:18 EDT 2009


Edson Tirelli schrieb:
> 
> "On the general issue, is it received wisdom that it's better not to insert
> map objects direct, at least for now until map support is fully there - or
> is it 6 of one / half a dozen?"
> 
>    Maps are data structures, not Domain entities.

When we speak about equivalence in the mathematical sense then this
statement is not true, as you say yourself:

> Using maps as domain entities is possible,

Exactly.
Instead of a class Point with the fields x and y we could have a Map
with the three slots "x", "y" and "type", where the makePoint method
would always return a Map whose "type" key always is set to "Point".

In Clojure this is an idiom. (Clojure is a language which compiles to
Java Byte code and is fully compatible to the rest of Java.)


 > but usually makes your rules unreadable.

You are so deeply trained to think in Java that you forget the
possibility of other syntaxes, in which it is perfectly readable.

 > That is  why it is bad to use any data structures or simple numbers,
 > strings, dates as isolated facts...
 > they don't have a well known business  semantic in a given
 > business model (not to mention how they get mixed
> with each other and cause cross products, etc). A rule like the 
> following has no explicit meaning:
> 
>        when
>                $str: String()
>                $m: Map( this[$str] == 1 )
>        then
> 
>     But when you write something like:
> 
>         when
>                Customer( $custId : id )
>                DailyOrders( count[$custId] == 1 )
>         then
> 
>     Things are clear just by looking at them, even if $custId is a 
> String and count is a Map as in the original example.

But now think about how this could be in Clojure.
We would have a macro map-rule which tells Drools that we are using
exclusively maps, and it may look like:
(map-rule "Rule name", "type"
   (when
     "Customer" ( $cust-id "id" )
     "DailyOrders" (= 1 (get "count" $cust-id)))
   (then
     (println "match")))

The first argument is here "type" and tells the macro, that all maps do
have a key "type". It now dispatches to those. In the when part we first
look at all Maps of type "Customer".
They may look like:
{"id" 4,  "name" "Carlos",  "type" "Customer"}
{"id" 1,  "name" "Tina",  "type" "Customer"}

Then we look at all Maps of type "DailyOrders". Those Maps may look like:
{"id" 27,
  "day" <java.util.Date Object>,
  "count" {4 "abc",  18 "xyz"},
  "type" "DailyOrders"}

get takes the key under which a DailyOrders instance has another Map as
value, and accesses is with the key which is the second value here,
the $cust-id.
The when part looks exactly the same, only that I did not use the
syntactic sugar for accessing maps.

As soon I have enough time in the coming days I will write a Clojure lib
which will make Drools easily usable from within Clojure.
The good thing about that is that rules are not longer strings - they
now become code. And because in Clojure  Code = Data  we will have the
full power of Lisp available, and can write programs that write programs,
such as rules. This easily opens rules to be automatically created via
Genetic Programming, or, if the search space is not too huge, then all
combinations of rules can be generated and tried out, to see which of
those 18000 can solve my domain specific problem best.


Currently the rule I showed above would become a Drools rule like:
rule "Rule name"
  when
   m1:clojure.lang.APersistentMap()
   m2:clojure.lang.APersistentMap()
   eval( m1.get("type") == "Customer" )
   eval( m2.get("type") == "DailyOrders" &&
         (m2.get("count")).get(m1.get("id")) == 1 )
  then
   ((clojure.lang.IFn)globalVarWithClojureCode.get(17)).call())
end

This would happen under the hood, and it would not be exposed to the
users of my lib. I did not test this specific example code, and maybe
I got something wrong, but that is the basic idea. Drools can work with
this and does not have the readability problems of humans.
In Clojure we can easily eliminate all readability isseus. I will offer
some few macros for defining rules, and users can simply add others if
they want. They won't have to care about what the underlying real Drools
rules syntax looks like. They only expand their macros into my existing
ones and have automatically optimized code, adopted to their readability
needs in their specific domain.


Now if the Drools Devs find a way how it would be possible to add the
same optimization support for Maps as they currently already exist for
POJOs, then rules acting on Maps could be very fast.
I understand that the call to the method get() itself will be slower,
because a hash value needs to be computed before the lookup can be made,
but I think that lookup will not be the bottleneck.
It would be fantastic if the Drools Devs could make it the bottleneck.
In that case, that Maps would not need to be placed in eval anymore and
become 1st class objects, exactly as POJOs, then get() would be the
slowest part (which is now eval and full execution without caching).

And Clojure Maps are also immutable. Drools can be certain that nothing
in the Maps changes (btw, Clojure vectors and lists are also immutable),
and that may lead to even more optimizations.
Perhaps there could be another method for inserting objects, such as
session.insertImmutable() or session.insertPersistent().
Inserting an object X in such a way would tell Drools that X can't be
mutated and thus other optimizations can be applied.


André
-- 
Lisp is not dead. It’s just the URL that has changed:
http://clojure.org/



More information about the rules-users mailing list