Drools Language Enhancements

 

Nested Objects

 

We currently allow nested accessors  to be used as follows, where address is the nested object:

Person(  name== "mark", age == 34 address.city == "london", address.country ==  "uk" )

 

So the main pattern has an outer pair of  parenthesis and then a set of 0..n bindings or constraints '(...)'.

 

'.(....)'  can also be used to allow access to nested objects, that mirror the  structure of the main pattern's parenthesis, providing more readable  rules. Note the '.' prefix, this is necessary to differentiate the  nested object constrtaints from a method call:

Person( name==  "mark", address.( city == "london", country ==  "uk") )

 

There  is no difference between:

Person( name== "mark", age == 34,  address.country == "uk" ) // nested  address object

Person( name==  "mark", age == 34, address.(country == "uk") ) // nested  address  object

 

Casting Nested Objects

 

When  dealing with nested objects, we may need to cast to a subtype, we can do  this via the # symbol. This example casts Address to LongAddress making  it's getters available. If the cast is not possible (instanceof returns  false), the evaluation will be considered false.

Person(  name=="mark" address#LongAddress.( country == "uk" ) )

Person(  name=="mark" address#LongAddress.country == "uk"  )

 

We  will need to support fully qualified names too:

Person(  name=="mark" address#org.domain.LongAddress.( country == "uk" ) )

Person(  name=="mark" address#org.domain.LongAddress.country == "uk" )

 

Q: How do we konw that country is a field, and not part of a FQN? possibly #org.domain.LongAddress#

 

Logic programmers typically use ^^ for casting ^^LongAddress, is another option.

 

Collections  and XPath like filtering

 

Xpath type filtering of a  list.

$per : Person( $pet: pets[ #Dog( age == 15 ) ] )

 

#Dog  says we are casting each eleemnt in the collection to dog and then  filtering it. Any standard pattern syntax is allowed inside of the  '(.....)' parenthesis. The # is also there to differentiate from method  calls such as last(). The above is internally semantically the same as:

$per  : Person( $pet: pets )

$pet : Dog( age == 15 ) from $per.pets

 

That  means if we had 3 pets of type Dog and aged 15, we'd get three  resulting "rows" and thus 3 activations.

 

Methods can  also return collections which we can treat the same.

$p : Person(  $a : someMethodCallToGetAddresses(...)[ #LongAddress( location ==   "london") ] )

 

$pet here would still be a reference to  the collection itself and not the elements.

$per : Person( $pet :  pets )

 

empty [] would force full iteration, so $pet  would reference each element in turn.

$per : Person( $pet : pets[]  )

 

Only one filtering collection, [] or [  ObjectType(...) ], per pattern is allowed i.e. the follow is invalid.

$per  : Person( $pet1 : pets1[], $pet2: pets2[] )

 

Methods/Functions/Expression  are allowed http://www.w3schools.com/Xpath/xpath_functions.asp

$per : Person( $pet : pets[ last() )] )

 

If  a collection filter is used on a map, instead of an array, it filters  the values not the keys.

 

Managed  Object Graphs (MOGS)

So far when dealing with nested object  graphs, we have assumed they are  not inserted and the engine does not  know about them.

Person( address.(location == "london", country ==  "UK" ) ) // engine does  not know about address

 

But if  Address is inserted and meta data is available to tell Drools that a  nested object exists also as an inserted object, as it's  "managed" with  implicit joins (related to how ontology relations will  work). The user  can write:

Person( address.(location == "london", country == "UK"  ) )

 

But we will rewrite this internally as follows, so  that the system  responds to changes in Address instances, note the  implicit generated join:

$p : Person(  )

Address{ owner ==  $p, location == "london", country ==  "UK" }

 

Unless we  know the object is immutable, in which case we should probably  keep it  as follows, as it's easier on the Rete network.

Person(  address.(location == "london", country == "UK" )  )

 

Nested Patterns and Queries

Not sure on this one, leaving heading for others to jot down ideas.

 

Escapes for Dialects

At the moment drools allows dialects to be used for any eval, return value or consequence. Instead drools should move to a single MVEL like language where we understand 100% of the syntax, instead of treating it like a black box evaluation. However some people may still like the idea of using their own language, so we should support the idea of dialect escapes.

then

   ...

<<java

    ... any java code here.

>>

   ....

end

 

To keep things compact we acn allow the escapes on the same line as the 'then' and 'end' in such a situation, it would work in a similar manner to the dialect attribute does now with regards to a consequence.

then<<java

    ... any java code here.

>>end

 

We can have as many of this dialect escapes as we want, and they may be in different languages. This would require us to define and publish SPIs, to make this integration easy.

then

   ...

<<java

    ... any java code here.

>>

   ....

<<groovy

    ... any groovy code here.

>>

end

 

Accumulate Improvements to Support Haskell map/fold/filter and MVEL projection/fold

 

This topic lists a number of accumulate improvements, both in function as well as syntax.

 

 

At the moment, "from" requires the use of "return( [ ... ] )" for inline list creation:

 

String() from return( ["foo", "bar"] )

 

Ideally, we should be able to write:

 

String() from ["foo", "bar"]

 

Use semicolon to separate the sections, allowing an inline constraint, hte last expression can be left blank if desired.

acc( Bus( color  == "red", $t : takings );                                              

       $min : min( $t ), $max : max( $t );
       $min > 100 && $max < 200 )

 

Map/Projections

 

  • accept expressions as function parameters:

map (+1) [1..5]  //haskel

 

Answer: this works as of Drools 4. Current syntax would be:

 

List() from accumulate( $n : Number() from return( [1..5] );

                                  collectList( $n + 1 ); )

 

accumulate should become acc

List() from acc( $n : Number() from [1..5];

                       collectList( $n + 1); )

 

parentNames = (parent.name in users); //mvel

List() from acc( $user : User() from users;

                       collectList( $user.parent.name ); )

 

 

acc( $user : User() from users;

       $parentNames : collectList( $user.parent.name ); )

 

And we can add a filter

acc( $user :   User( ) from $users and

     $parent : Parent( age > 30 ) from $user.parent;

     $parentNames : collectList( $parent.name ); )

or

acc( $user :   User(  parent.age > 30) from users;

        $parentNames : collectList( $user.parent.name ); )

 

 

familyMembers = (name in (familyMembers in users)); //mvel

acc(  $user : User() from $users and

        $p : Person() from $user.familyMembers;

        collectList( $p.name ); )

 

(toUpperCase() in ["foo", "bar"]); // returns ["FOO", "BAR"] //mvel

acc( $s : String() from ["foo", "bar"];

       collectList( $s.toUpperCase() ); )

 

(($ < 10) in [2,4,8,16,32]);       // returns [true, true, true, false, false] // mvel

acc( $n : Number() from [2,4,8,16,32];

       collectList( $n < 10); )

 

($ in [2,4,8,16,32] if $ < 10);     // returns [2,4,8]     //mvel

acc( $n : Number( this < 10 ) from [2,4,8,16,32];

       collectList( $n < 10); )

 

[x for x in [x**2 for x in range(10)] if x % 2 == 0] // python

inner list produces:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

thus result is:

[0, 4, 16, 36, 64]

acc( $n : Number( eval( intValue % 2 == 0 ) ) from

              acc( $n2 : Number() from [0..10];
                     collectList( $n2*$n2 ); );
       collectList( $n ); )

 

 

Left Fold

(((1 + 2) + 3) + 4) + 5.

Left fold starts from the left, i..e. 1.

acc( $n : Number() from [1,2,3,4,5];

       $result : sum( $n ); )

 

Right Fold

1 + (2 + (3 + (4 + 5)))

Right fold starts from the Right, i..e. 5.

 

We'd need to add an accl and accr for this, with acc defaulting to accl.

accr( $n : Number() from generateList( [1,2,3,4,5] );

     $result : plus( $n ); )

 

ANSWER: Right folding is really heavy at network level due to the way RETE works with greed evaluation. I would not recommend enabling right folding.

 

If we addd implicit variables functions and acc, we can get compact chaining.

acc( Number();

       sum() )

 

acc( acc(......);

       func() )

 

Finally we could support filter, map and folder as actual built-in function implementations.

acc( $n : from [1, 2, 3, 4, 5]
       filter( $n, $n > 5 ) )
     
acc( $n : from [1, 2, 3]
       map($n * 2) )
     
acc( $n : from [1, 2, 3]
       fold($ + $n) )    // $ would be a magic var that references the current "result"

 

These builtins would remove a lot of the need to use the init/action/result inline block code, when people want to inline some functionality. Haven't thought of a clean way to provide initalisation data for those functions, such as the inital value of a map

SQL Group Operators

 

Distinct

To  get distinct People, by location and age:

acc( $p : Person();

        [$l, $a] : distinct( $p.location, $p.age) ) // needs to support multi return value bindings

 

 

Aggregations (equivalent of group by)

To   perform aggregations over those distinct groups

select  location, age, avg(income), min(income), max(income) from Person group by location, age  //SQL

 

acc( $p : Person();
        [$l, $a] : distinct( $p.location, $p.age) )
acc( $p : Person( location == $l, age == $a );
       $avgIncome : avg( $p.income ) ,
       $minIncome : min( $p.income ),
       $maxIncome : max( $p.income ) )

 

If we wanted to make a  list of lists we could add:

acc( $l : List() from [$avgIncome, $minIncome, $maxIncome],

       list( $l )

 

 

Limit  and Order

in each location for each age print the 2 top salaries

select  * from Person group by location, age order_by income limit 2 //SQL

acc(  $p : Person() from acc( $p : Person() from acc( $p : Person(),

                                                      distinct( $p, $p.location, $p.age ) ),

                               orderBy( $p, $income ) ),

      limit( $p, 2) )

limit again can be done by a pipe:

$p : Person()  | distinct( location, age ) | limit ( 2 )

 

 

Intersect

Find  the people who bought things on their birthday

select Date from  Recepts

Intersect

select Birthday from Person

 

acc(  $r : Receipts()

          Person( birthday == $r.date ),

       list( $r.date ) )

 

Minus

select Date from  Recepts

Minus

select Birthday from Person

 

acc(  $r : Receipts()

          not (Person( birthday == $r.date ) ),

       list( $r.date ) )

 

** Is this efficient?

 

Union  All

the behaviour of an 'or' CE is like a union all

acc(  Receipts( $d : date ) ||

        Person($d : date ),

        $l : list( $d ) )

 

Union

Same as union all but adding a distinct:

acc(  (Receipts( $d : date ) ||

        Person($d : date )),

        $l : list( $d ) )

 

Unit support

Groovy added unit support, based around leveraging JScience and  JSR275. http://groovy.dzone.com/news/domain-specific-language-unit-. The dot notation is a bit too ambigous for Drools, but we already use # to cast patterns, so we can do the same for units.

3#km + 5#m

 

We can cast existing units to other units. So we can declare something as 3km, but have it returned as feet.

3#km#feet

 

If a unit is combined with a normal literal, then it's just a operator on that literal value, for instance the following is 6k, it is not executed in the same way as 3km * 2km would do.

3#km * 2

 

# can be used with methods and functions. If the method returns a literal, then it's the same as saying 3km. If the method returns a unit, then it's like a caste

returnInt()#km

returnKm()#feet

 

This works for strings too, and can be used to provide date/time formatting:

"2012 12 04"#myDateFormat

 

Otherwise

Sometimes you just want a catch all situation,
“else” does not provide this.

 

If I have 3 rules, each one checks a different capital location:
“london”, “paris” or“new york”

 

Yet I insert a fact where the capital is “athens”, how do I handle this
Unknown?

 

This is typically handled by decision tables by the use of the
“otherwise” value, which actually generates the following rule:
capital not in (“london”, “paris, “new york” )

 

Ignoring performance issues when this list gets very large, there
is more the issue that this is a solution for tooling – it's not a
good solution for direct drl authoring and requires manual
maintenance.

 

Enter “otherwise-group”. We can put all the related rules that
we would like to apply this scenario too into a group.

 

We then add a rule that will handle that “otherwise” situation:
Capital == OTHERWISE
The engine recognises that this is a special value and that the
rule is part of a “otherwise-group”. What will happen is that for
a given propagation on that ObjectType is none of the other fields
match then the OTHERWISE is considered to match.

 

This allows a fine grained and sophisticated way to handling
unknown values.

Pipes - Pass through Filters

see: Relational DataFlows

 

Branch/Switch/Case/Else (Labelled Else)

see: Relational DataFlows

 

Rule Execution Groups

generic rule group concept to replace hard coded agenda-group, ruleflow-group and the various rule attributes.

Drools Module

 

Parallel Meta-Rule Language

 

As starts to become more parallel, we need a meta-rule language to help orchestrate parallel firing. There are some papers on this already, if you search google scholar.

 

See papers related to Pararulel

 

Multi Version Concurrency Control (MVCC) and Transactions

see Drools Module

We need optional MVCC and Persistable Data Structures for more parallelisation. We also need to think about transaction semantics in the engine. While this is really an internal engine thing, I've included it here as it may have some impliciations for language design too, and for general awareness.

 

At the simplest level all changes to the facts in a consequence propagate through the network in parallel to the existing version of those facts. Activations on the agenda on the existing versions will continue to fire as normal. When the consequence is finished the now old version of those facts are retracted and the activations created as a result of this consequence are allowed to fire. As we have differential updated, we need to be able to share as much of existing data structures as possible. This means that the propagation will probably need to record retraction points in the network as command objects, that are executed as part of the consequence finishing. Obviously if the consequence fails for some reason, maybe it violates some description logic constraints, then the propagation is retracting and the network left as is.

 

In the case of distributed transactions that the engine is particapting in. The easiest option is that the engine observes, but does not react to those changes until after the transaction commit (shallow transaction). If we want to start thinking about including rule executions in those transactions, we have to be extremely careful due to rule recursions and the result side effects. We would probably need to look at some sort of rule group, and that all changes would be isolated to that group until after the transaction is finished.

 

Another thing to remember is that this type of functionality combined with the collection-oriented match for parallelisation, have serious implications for determistic execution, which we haven't found a good solution(s) for yet; see Parallel meta-rule language.

 

Field Versioning

There are times when you need to compare between current and previous values of a field, users can do this now by intermediary facts; i.e. inserting an Event to represent the before and after value for a field change, but it's a little clunky. Intead we can provide built in support for this into the language, using an @ver(int) attribute. The idea is that Drools would store previous values, only on demand, so you only pay the cost if you use this feature. The value for @ver is an effective "diff" value counting backwards starting from 0, which is now. So @ver(0) is the current field value, @ver(-1) is the previous field value @ver(-2) is the value 2 versions ago.

SomeFact( fieldName != fieldName @ver( -1 ) )

 

so any field with no @ver is effectively, and you could write it as such, @ver(0)

SomeFact( @ver(0) fieldName != fieldName @ver( -1 ) )

 

We can allow bindings to previous versions

SomeFact( $var : @ver(-2) fieldName )

OtherFact( field == $var )

 

We should also support the ability to add a range of values to a list, for processing with accumulate

SomeFact $list : @var(0....-5) fieldName )

Logical Closures/OnFalse

Allow logical support for a closure. with the logical support becomes false the code block will be executed.

when
    $l : Light( status == “on” )
then
     … do some stuff....

     logicalClosure( ) {
         println( “light has gone off” + $l );
     }
end

 

We analyse the logical closure to enclose the necessary variable from the outer scope. When the LHS is no longer true, the closure is called, with the enclosed state.

*

If we where to instead start to expose events that could easily be attached onto for single execution, we could get something more orthogoal in nature.

 

event.onFalse( new def() {

    println( "light has gone off" + $l );

} );

 

The later is more preferable but it ties up with consistency of exposing other events for calls backs else where, like on activation creation and and rule firing.

Logical Modify

Field changes that are undone when a rule stops being true.

when
    Document( status == “valid” )
    $p : Person()
then
     logicalModify( $p ) {
         status = “valid”
     }
end

 

** What happens if multiple rules “logical modify” the same
field and one loses it's justifications?

Lambda Support with Analysis

We should support lamdas, ideal for flexible goal based development.

 

rule "Schdule Location Update"
    $location : Location()
when
then
    insert( new Executable( def() { modify( $location) { cell++ } } ) );
end

rule "update phase"
when
    $exec : Executable( phase == "update phase" )
then
    $exec.call();
end

 

Notice in the above we analyse the def() to determine the variables needed from the outer scope and capture those, so the are available at the time of calling call(); so they work like a closure.

 

** We need to be careful here as it creates a very flexible system, we need to make sure that as much verification and analysis as possible can be done here.

 

Rising / Falling edges

Grindworks supports the idea of  executing actions on the rising or falling edges of a rule. While we  could do this on the actions too, we think initially this would be  better on the LHS, as a special conditinal element.

when

     rising Person( age == 30 )

 

when

    falling  Person( age == 30 )

 

Clealry rising is the default  behaviour for a pattern. Whether we allow it's inclusion, for  readability intent, or only support falling, is to be decided.

 

We could combine this with a branch

when

    ...

   branch( rising Person(....),

               [b1]  falling Person(....)

 

This also has some ramifications  for Logical Closures, as it solves some of the same problems.

 

Pluggable ObjectType Support

In theory Drools is object type  independant, the reality is that currently it's hard to use anything  other than Pojos. There is support for an unnoficial feature called  FactTemplates, which is a bit like dynabeans. We need to fully abstract  the ObjectType semantics and their relevant read/write capabilities and  make it pluggable.

 

Singe Pass Insertion

Quite often we want a fact to propagate through the network and find possible joins, after that propagation is finished we do not want any more join attempt matches. We can achieve this either via type declarations or support for special types of assert.

**Is this already possible with experiation 0s?

 

Single Match/Consume

Sometimes you want a fact to match once, and only once, against a fact for any given cross product and then remove itself.

Realtime Verification and Analysis

 

Traits, Duck Typing and Dynamic Semantic Learning

see http://blog.athico.com/2011/07/traits-duck-typing-and-dynamic-semantic.html

 

Federated Data Sources for Queries

Drools has a registry of querries, with their own  execution handlers. We  provide the handler for our AST, and it  generates the execution plan (we  don't care how). So a DB table can be  registered as a hibernate source:

?TableName( fieldName == "x",  fieldName2 == $y, $v : fieldName3 )

The registered query may  optionally support positional, or POSL
?TableName( "x", $y, $v )

When we have objects from the same, non drools, data source we  will  attempt to allow the registered data source handler to optimise,  to execute both together at the data source side.
?Table1( fieldName1  == "x", $v1 : fieldName2 )
?Table2( fieldName2 == $v1, $v2 :  fieldNam2 )

Instead of calling ?Table1 and then calling ?Table2  we'll make a single  call and let the data source handle it, and it  will return $v1 and $v2  as a result.

This gets a bit more  difficult with not and exists. We'd need to  recognise we have a self  contained data structure that can be passed out  to the data source  handler.
?Table1( $v1 :fieldName1 )
exists( ?Table2( fieldNAme1  == $v )

The federated data stores start to take a lot of  relevance once we build  in the opportunistic backward chaining.

 

Event Sequencing

State Machine for detecting sequences in events.

 

"->" operator should be supported for meaning "followed by". This can be used between patters as normal. However we should include a new 'seq' CE that supports a simple state machine DSL. The "end" and "start" placeholders are special ones indicating the start and end of the state machine. Any patterns after the 'seq' element will not return be reached until an 'end' has bee triggered.

seq (

   start  C() -> end

 

   start D() -> end
   start $e : E() -> $f : F( this after[1s, 10s] $e) -> name1
   name1 C() or B() -> end
   end E()

)

 

Anything inside of the seq CE can join with previously bound variables.

 

What would be nice is a qay for the seq elemen to return some state too; maybe so we can deal with different types of returned state; might be idea for the 'branch' CE we have proposed.

Field Sequencing

To write unambigious rules for mutable data we need to be able to constrain a pattern to be true for a given sequence;

Light( someField=="blah", seq(color, category) {
  start "red", "x" -> "green",   "x"  ||
                            "yellow",  "y"
                        -> "blue", "z" -> end
  end
} )

In the above we have a seq for the color and categroy fields. All states must have the same number of arguments as the given parameters.

Uncertainty / Vagueness

 

A constraint such as Person( age > 30 ) requires that Person.getAge() returns a definite value, precise and certain: the restriction, then, is either satisfied or not. But the value may not be known with such precision: getAge() may return

- null : or a special value denoting that the value is missing or unknown

- a probability distribution over a set of possible ages - e.g. { 18/70% , 19/28% , other/2%}

- a possibility distribution (aka fuzzy set) - {15/0.1, 17/0.5, 18/1, 19/1, 20/0.5, 22/0.1} – or a linguistic value such as "young"

- combinations thereof

It is necessary to generalize the concept of evaluator and the type of result it returns, since a boolean is no longer adequate. Instead, probability degrees and gradual degrees of truth are more appropriate results. To do so, we require custom evaluators and meta-attributes. For example:

   Person( age ~greaterThan @[ kind="myGTImplementation" params="..." ] 18 )

where "greaterThan" is a custom, uncertainty-aware evaluator. Its concrete implementation, be it the default one that extends the boolean case or a user-provided definition, determines whether the constraint is satisfied (and to what degree) to the best of the available knowledge. In a similar fashion, a notation such as:

   Person( age ~greaterThan @[ degree="..." ] 30 )

could be used to assign a "prior" degree to the constraint, to be used in place of (or together with) the value returned by the evaluator: the simplest example is a probability when the value is missing in the fact under evaluation.

Similar considerations apply to logical connectives as well, since they no longer combine booleans. The specific implementation can be controlled using the same attributes:

   Person( ... && [ kind="Product" ] ... )

Eventually, it could be convenient to extend the connective set with logical negation, implication, exclusive-or and similar constructs.

Learning/Decision Trees

 

Ordered facts

Ordered facts come from Clips - the idea is that a "fact" can be thought of as a list of field values - there is no "fact type". These facts are then matched based on field values (obviously) but also the number of fields in a fact. Some times people think of these as "anonymous" facts.

 

Ordered facts are really just the data - in a simple flat structure, often when creating a class/declared type is overkill. They allow for both rapid development of rule logic (no need for too much upfront thinking/modelling on the facts) as well as for very concise readable rules.

 

Example (from clips)

 

   (person John S. Liu     ;name John
            23              ;age
            brown           ;eye-color
            black)          ;hair-color




When matching these facts, you specify what values/ranges you are looking for in a given "slot" (an ordered fact is a list of slots - slots are fields) - and if you

don't care about the value of a slot - you have to match with a wild card (as the number of slots is included when looking for a match).


The challenges for this in drools are both in terms of syntax, but mostly in terms of types: you don't know up front what the types of the slots are - so you almost have to treat them as Object.


Some possible ways it would look:


 

    insert( [["John S", 23, "brown", "black"]] )

    when

       [["John S", ?, "brown",  ? != "pink"]]


See:
http://www.csie.ntu.edu.tw/~sylee/courses/clips/intro.htm

  

 

 

Done

 

Free form Expressions

 

The  idea of returnvalue and inline-eval needs to go. We just have  constraints, it's up to the builder to figure out how to execute those,   and index if possible. Those constraints should support any valid MVEL   like expression either side of the operator

Person(  pets["rover"].age == $otherAge.someMethod( $x ) + 3 ) // notice  no  (...) delimeter like on return-value

Person( pets["rover"].age ==  ($otherAge.someMethod( $x ) + 3 ) / 2 )  //  this has the (...) but it's  to indicate ordering in the expr evaluation only

                                                                       // so the / 2 is last

Person( pets["rover"].age * $v1 == $v2 - 3 )  // expr can be on both sides

 

If we have collection  filters, $p will exist for each resulting  red pet  with appropriate age

Person(  $p : pets[ #Dog(color == "red") ].age * $v1 == $v2 - 3 )

 

Although  that also can be re-wrriten, as mentioned previously

Person( $p :  pets[ Dog( color == "red", age * $v1 == $v2 - 3 ) ] )

Positional  Constraints

While Java focuses on slotted, key value, type  classes. It can be desirable to work with positional terms, like with  prolog, especially when unification querries are involved. Initially  positional information will be obtained via a field annotations -  @Position(int). DRL type declarations will automatically generate those  annotations for beans it generates. The new nested syntax is leveraged  here for nested terms:

Person( "mark", 34, address.("london",  "uk") )

 

** for future reference ** One thing we are  asked for too is ordered lists, like Jess/Clips. Where  you can do in  Clips:

(mary rode a duck)

 

Jess/Clips provides a  sort of pattern matching language for matching  those lists. In  Jess/Clips this is often used to provide english like  sentences, or  checking contents of a list. But I don't think we can  support that in  such a succinct way. Instead I just think we should add  generic support  for a pattern matching on a list content. We can expand  on this  another time, it's more of a heads up for ordered facts (of  mixed  length) compared to fixed length terms.

Person( list == ....some  list content matching crap here.... )

 

See "Ordered Facts" for more details.

 

POSL -  Positional-Slotted Language

Slotted and Positional can be  combined with POSL, allow the user to construct constraints in a mixed  way, of their choice. With POSL you have 0..n first arguments mapping to  positional, after that any arguments can be 0..n slotted.

Person(  "mark", age == 34, address.("london", country == "uk") ) //  nested  address

 

Method Calls

 

'(' ')' for  method calls. You can now see why the '.' prefix for nested objects was  added, to allow methods to be differentiated, to avoid ambiguity. Unary  method calls are only allowed if used with a binding, otherwise it is  considered passing a value to a positional argument.

Person(  someMethod(....) == $x, address.someMethod(.....) == $y, $z :   someOtherMethod(...) )

 

Maps and Arrays (Collections)

Person(  pets[0].age == 15 )

Person( pets["rover"].age == 15 )

 

Queries  and Unification

 

Queries use POSL syntax. Variables  not previously bound to a value are considered a Unification Variable.  Querries can be used in rules.

when

    $p : Person

          ?queryName( "literal", $p, $unificationVar )

then

     println( $p + ":" + $unificationVar )

 

See "Query Based Backward Chaining with POSL" for more details on querries.

Query Based Backward Chaining with POSL

Queries should be extended to support unification points and POSL. Further a rule (and thus a query) should be able to call another query.

class, position is assumed in field declaration order
Person {
    String name;
    String location;
    int age;
}

 

positional
Person("darth", "london", 105 );

 

slotted
Person( name = "darth", location = "london", age = 105 };

 

mixed positional and slotted instantiation
Person( "darth", location = "london", age = 105 }

 

Data:
Person("darth", "london", 105);
Person("yoda", "london", 200 );
Person("luke", "paris", 40 );

 

Slotted query :
Person( $n, "london", $y );
Person("darth", "london", 105);
Person("yoda", "london", 200 );

 

positional query:
Person( $n : name, location == "london", $y : age );

 

mixed query:
Person( $n, location == "london", $y : age );
Person( $n, $y : age, location == "london");
Person( $n, "london", $y : age );

 

Existing Drools Queries, more like SQL, all arguments are "in":
query queryName1(arg1, arg2, arg3)
    $o1 : Object1( field1 = arg1 );
             Object2( field1 = $o1, field2 = $arg3)
end

 

and querries are called with positional only
?query( “value1”, “value”, “value3” );

 

When calling lets allow arguments to be specified or
have variables passed for unification. We can even then call
other queries.
query queryName1(q1arg1, q1arg2, q1arg3)
    $o1 : Object1( field1 = q1arg1 );
             ?queryName2( $o1, q2arg2 == q1arg2, q2arg3 ==  q1arg3 )
end

 

query queryName2(q2arg1, q2arg2, q2arg3)
    $o1 : Object2( field1 = q2arg1 );
             Object2( field1 = q2arg2, field2 = q2arg3)

End

 

So now we can all this with positional and slot:
queryName1( “value1”, $a2 : q1arg2, $a1 : q1arg3 )
queryName1( “value1”, $a2, $a1 : q1arg3 )

 

We should also allow rules to be querries, so actions can be executed on each rule. This will will trigger a DroolsQuery object is inserted, either manually by the user, or internally by the getQueryResults method call.

rule ruleName( q2arg1, q2arg2, q2arg3 )

when

    Pattern( field2 == q2arg1,

                field2 == q2arg2,

                var3 : field3 )

then

    set( q2arg3, somFunc( var3 ) ) // how do we know this is a unification variable, can we use branch?

end

 

We should support "open querries" as well as the more normal closed, single execution ones. "open querries" will continue to push through now full patches to the calling parent node.

 

Rule Dependency Meta-Rule Language

When a terminal node is matched instead of adding the Activation to the agenda it inserts it into the WorkingMemory. We have a special builder that allows easy access to the contents.

All declarations are typed fields for the Activation fact, based on the "name" field. So the name field is mandatory. All FactHandles are available via an array accessor, which has type inference for the element being used. We also all bindings on the Activation fact to work this way too. Act is used for compactness, we'll allow that to be optionally user defined.

act1 : Act( someDeclaration == X, fact[0] == Y )

act2 : Act( someDeclaration.value > act1.someDeclaration.value )

 

Normal facts can also be matched. The RHS of the rule is side effect free, you cannot modify or insert facts; this allows the RHS to execute as soon as it's matched. What you can do is setup rule dependencies - where one rule blocks another.

act1.blockedBy( act2 ).until( Fired )

act1.blockedBy( act2 ).until( IsFalse )

 

We can even allow facts to block,

act1.blockedBy( someFact )

 

This means the act1 activation is blocked until a rule executes:

act1.unblockedBy( someFact )

 

We can probably add an override, something like

act1.unblockAll()

 

Only when an Activation is no longer blocked will it be placed on the Agenda as normal.

 

If an activation on the agenda has not yet fired and something attempts to block it, it will be removed from the agenda until it is no longer blocked.

 

For this to be effective, especially for large systems, it will need to  be combined with design time authoring help.

 

This work will be eventually be combined with further enhancements to help with parallel execution, in resulting conflicts, see the "Parellel Meta-Rule Language" heading. This might be able to be combined with prova like "trusted" "guard" features, http://www.prova.ws/csp/node/19

 

Slot Specific Modifies

Currently when you do a modify all patterns are tested and if true the tuple is propagated; regardless of whether it constraints on the changed field or not. Instead the behaviour should change so only rules which read the changed field receive the propagation. This is similar to "slot specific" in Jess and COOL in Clips. It is not enough to just see if the Pattern itself constrains on a field, we must check all bindings and functions and even consequence; to detect which fields it relies on. Then at compile time any modify should contain a list of sinks that it should restrict it's propagation too. Additional work will have to be put into thinking about nested objects. Calling "update" will propagate the tuple as the per existing behaviour, propagating to all patterns. A pattern should also be able to request to listen to changes on some or all fields, regardless of whether it constraints on it or not.

 

I've done a more recent blog on this idea, using a field listener syntax

http://blog.athico.com/2010/07/slot-specific-and-refraction.html

 

Ontologies and Relations via Triples with Hybrid POJO Graph Notation.

We can build on the managed collections work work to allow implicit relations.

$p : Person()

Pets( owner == $p, age > 30 )

 

Could be allowed as:

Person() IsOwnerOf() Pet( age > 30 )

 

We can also allow hyrbid nested access and triples. with the above as

Person() IsOwnerOf() Pet() HasAge( this > 30 )

 

I have done an updated blog article on these extened with the Triples idea, first proposed by Davide Sottara.

http://blog.athico.com/2011/07/traits-duck-typing-and-dynamic-semantic.html

 

Opportunistic Backward Chaining, Lazy Field/Object Values

Branch can be used to provide opportunistic capabilities for creation and/or assignment of field and object values, leveraging backward chaining.