Drools 6.0

Grouped Accessors for 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

 

Inline Casts and Coercion

hen  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 its 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" )

 

**ET: it seems to be that FQN casts will be very expensive to compile, specially because the same notation dot notation is used for the package name and for attributes. The engine has to test each element in the FQN to find out if it is a class name, possibly raising the CNF exception, and after found, it has to test for attribute or subclasses. We might want to use a delimiter for casts, at least for FQN casts. For instance:

**MDP: Do we incur any costs for non FQN? in the vast majority of cases it will NOT be FQN. a handful of cases will have class name clashing, to which FQN help. If the cost is only on the time it's used, then I think it's ok. We can document as "best practice" not to use it, for parser performance issues.

 

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

 

 

It should be also possible to use inline cast in the right part of an expression like in:

 

Country( name == $person.address#LongAddress.country )

 

and to use multiple inline casts in the same expression:

 

Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )

 

Note we also support the instanceof/isa operator; if that is used we will infer its results for further uses of that field, within that pattern.

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

 

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 ambiguous for Drools, but we already use # to cast patterns, so we can do the same for units, making coercion and casting the same thing. Note that we will probably need to "import" any types we wish to coerce to, to ensure type safety and ensure coercion. We'll need to find a specail way to make units importable and accessible under their short alias syntax - i.e. #km instead of Kilometre.

3#km + 5#m

 

** ET: As I mentioned before, I find it clunky to read (and type) # for unit representation. I was wondering why not simply use the units as sufixes to the literals:

 

3Km + 5mt

 

Of course we will need to find proper sufixes for all units we want to represent, as we already use some sufixes for time intervals:

 

3h41m32s

 

** WL: Simple suffixes on literals are apt to cause confusion with Java's type suffixes: l = long, or is it litre? s = short, or is it second?

 

 

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

3#km#ft

 

**ET : I don't think the above is correct. The unit literals will create instances of their unit type, and not of the unit of the literal used. For instance, 3Km will create a, lets say, "Distance"  instance, not a "Km" instance. In that context, the above "cast" does not make sense.

 

If a unit is combined with a dimensionless literal, then it's just an operation on the unit's numeric value. The following expression yields 6km, in contrast to 3#km * 2#km.

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

return returnsInt()#km

return returnsKm()#ft

 

** ET: The above also is something I think it is not orthogonal, as it is mixing literal "concatenation" with unit arithmetic. If I want my method to return a Distance in Km, I would do:

 

return returnsInt() * 1km

 

 

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

"2012 12 04"#myDateFormat

 

** ET : We can define syntax sugar for unit formating using the # syntax, but we will have to extend that to work with all supported units.

Collection Filtering and XPath like statements

The fordward slash '/' provides XPath-like collection filtering. The # without a class assumes type inference.

Person()/pet#( name == "rover" )

**Do we really need the #, it feels consistent though.... with type inference with switch/case, where it seems more obviosly needed. We are trying to achieve consistency. Maybe both cases don't need it.

** ET: +1 on not using # when there is no cast

 

We can use # to filter for specific subclasses, e.g., with Animal pet and Dog extends Animal we could write

Person()/pet#Dog( name == "rover" )

 

We can bind to the Person, or to each resulting pet that's a Dog:

$pr : Person()/$pt : pet#Dog( name == "rover" )

 

Note that while the inline cast to match against subclasses uses #, it does not use the ".", that inline cases use inside of patterns. The reason for this is that pet#Dog is still matching against a set of objects, whereas within the pattern the inline case is operating on the current object. This is simlar to using 'from'.

$p : Person()
Dog( name == "rover" ) from $p.pet

 

Any expression can be used after the /, including multiple nested essor calls and method calls. If the last essor returns a single instance rather than a collection, it'll just attempt to match that. With List<Animal> pets and Dog extends Animal we can write

Person()/pets[0]#Dog( name == "rover" )

The above pattern will yield the same results as:

Person( pets[0]#Dog.( name == "rover" ) )

 

It is possible to execute multiple collection filters on the same instance, but they must be done as separate statements. One will be executed as an inner statement of the other. In the following example for each "rover" dog found, it'll find all other younger dogs in the same collection.

$p : Person()
$d1 : $p/pet#Dog.( name == "rover" )
$d2 : $p/pet#Dog.( age < $d1.age )

 

Flow operators with XPath like notaiton

 

The following represents a TMS chain between a Justifier and the Justified

$f1: Fact()
$d : LogicalDependency( justified == $f1 )
$a : Activation()  from $d.justifier
$f2 : Fact() from $a.facts

 

"<" means reverse the flow of the join.

$f1: Fact()
$d : LogicalDependency( justified == $f1 )

could be rewritting as

 

$d : LogicalDependency( justified == $f1 ) < Fact()

 

 

Note we only allow a single direction, no "tree" syntax as previous proposals is allowed.

 

We can further addd implicit variables using $n where n is the index position  of the fact. Note we can either count n form the root of n backwards from the current pattern. For now I'll do the prior.

 

$d : LogicalDependency( justified == $f1 ) < $f1 : Fact()

 

becomes

 

$d : LogicalDependency( justified == $1 ) < Fact()

 

 

'|' is a possible 'from' symbol

 

$a : Activation()  from $d.justifier

 

becomes

 

$a : Activation()  | $d.justifier

 

 

We can allow '<|' to be combined. This means we start with a 'join' but the result is piped to the next pattern. We could argue it should be '|<'. Using those two symbols, combined with our xpath notation, we can rewrite the original statement as:

 

Fact() | Activation()/facts <| LogicalDependency( justified == $1 )/justifier < Fact()

 

Note in the above the Fact on the far right, is from the WM. The LogicalDependency is also from the WM, joined with the far right Fact. The Activation and the following right sided Fact pattern, are from nested accessors.

 

We can possibly make () optional, when used with '/', if you have no constriants.

 

Fact/ | Activation/facts <| LogicalDependency( justified == $1 )/justified < Fact/

 

 

Assignments

We can assign the results of an expression to a variable using a block in braces:

$n : { 2 + 2 }

 

This is like 'from' with type inference

$n : Number() from 2 + 2

 

Type declaration is possible using the "unit" syntax.

$n : { 2 + 2 }#Double

** this needs more thought

** ET: we need to be aware that users will start to use arbitrary code in these expressions, but I like the feature.

Blocks

Keywords for blocks "then...end" work well for top level keywords, but do not work well for nested blocks. Curly brackets work well for nested blocks, however if we keep keywords for top level and curly brackets for nested blocks, it looks strange. Instead labeled curly braces is proposed. (Look at the test and switch functionality, to see where this becomes relevant.)

 

rule r1 when {
    ...
} then {
   ...
}

**  I disagree with keywords not working well for delimiting blocks - if used consistently. But going towards the C/C++/Java style isn't bad.

 

** ET: We need to be careful with ambiguity, specially with the arbitrary code blocks, but I have no problem in introducing curly brackets.

 

 

Then Blocks

Then blocks allow multiple rules to be combined into a single rule, in  manner similar to rule inheritance. The following example is the equivalent of 3 rules, each inheriting from the parent.

 

rule xxx when {
    A()
    B()
    then[t1]
    C()
    D()
    then[t2]
    E()
} then {
   // executes with A, B, C, D, E
} then[t1] {
   // executes with A, B
} then[t2] {
   // executes with A, B, C, D
}

 

//  alternative syntax 
rule xxx {
   A()
   B() }
then {
  // executes with A, B
} when {
  C()
  D()
} then {
  // executes with A, B, C, D
} when {
  E()
} then {
   // executes with A, B, C, D, E
}

Conditional Blocks

MDP (2 sept 2012) this has been rethought, and we now in agreement of simple 'if/else if/else' and 'switch' conditional elements. This has been highlighted in more detail here, we will merge back into the main document, when details are resolved.

Conditional blocks use the keyword "test". They are contextually linked to the last pattern, which provides the "this" object. If the test fails the 'fail' block is evaluated. The example below is effectively two rules, one rule where the tests pases and one rule where the test fails.

rule xxx when {
   $p : Person()
   test( age > 30 ) fail {
      $b : B()
      break[b1]
   }
   $c : C()
} then {
    println( $p + ":" + $c );
} then[b1] {
    println( $p + ":" + $b );
}

 

MF - I don't like the use of 2 keywords here when only one could be enough. Moreover it is a bit counterintuitive that you say 'test' and then you can say only what you want to do if that tast fails. I'd prefer something like: ifnot( age > 30 ) { ... }

 

** ET: I agree with mfusco here. A few comments:

  • Why not use eval() instead of create a new keyword test()?
  • Why not make "fail" a CE sufix instead of only working for test? In that case do we also need a "succeed"?

Person( age > 30 ) fail { ...}

  • I am a bit worried about the interactions of "break" and "then" as defined here. It seems to me that it will be confusing to have evaluation continue past a "then" instruction, but I can be convinced otherwise.

 

//  *** Very confusing - this is a "goto" in LHS code - shudder!
rule xxx when {
   $p Person()
   test( age > 30 ) fail {
      $b: B()
   } then {
      println( $p + ":" + $b );
      break;                          // leave everything
   }
   $c : C()
} then {
   println( $p + ":" + $c );
}

 

Note however the 'break' keyword. 'break' ensures the joins from Person to C do not happen if break is called. i.e. only one branch can execute.

 

In the example below the then[f1] is optional, and will create an Activation to execute on the agenda. If the then[1] is not specified, no Activation is created. The following is perfectly valid, and will cause both branches to be evaluated, and thus activations for Person, B and Person, C will be created.

 

rule xxx when {
   $p : Person()
   test( age > 30 ) fail {
      $b : B()
      then[f1]
   }
   $c : C()
} then {
    println( $p + ":" + $c );
} then[f1] {
    println( $p + ":" + $b );
}

 

breaks will only break the current block, not all outer blocks. In the following example if age is above 30,  Person will join with B; the nested test is hard coded to fail, thus we never reach t1. However as the outer test has no 'break' it will always still join with C.

 

rule xxx when {
   $p : Person()
   test( age <= 30 ) fail {
      $b : B()
      test( false ) fail {
           break
      }
      then[t1]
   }
   $c : C()
} then {
    println( $p + ":" + $c );
} then[t1] {
    println( $p + ":" + $b );
}

Switch Blocks

MDP (2 sept 2012) this has been rethought, and we now in agreement of simple 'if/else if/else' and 'switch' conditional elements. This has been highlighted in more detail here, we will merge back into the main document, when details are resolved.

 

Switch blocks allow us to "case" match against a given instance, which is passed in as the expression after 'switch' and defines the 'this' context for the entire 'case' statement. Where a 'case' match is successful, pattern joining is done. The 'case' phrases are not alternatives, but 'break' is available to terminate the switch CE after a match. In any case, the 'default' block is executed only if no other 'case' phrase results in a match.

 

The type is inferred from Person, via #. However you can also filter subclasses or implementations, via #Worker.

rule xxx when {
   $p : Person()
   switch( $p ) {
     case #( name == "john" ) {
         $b : B() from new B(1)
         break
       }
     }
     case #Worker( name == "john" ) {
         $b : B() from new B(2)
         break
     }
     default {
         $b : B() from new B(3)
         break
     }
   }
   A( field = $b )
} then {
  .....
}

 

Conditional branches can be nested in switch blocks, and vice versa. However the break only breaks the current block.

 

**Do we really need the #, if no class is specified. Likewise do we need the # at all, even if it is. It feels like # should be there, just to signify there is a type, even if inferred. See collection filter. We are tryig to achieve consistency. Maybe both cases don't need it.

** ET: I would not use #, unless a cast is necessary

 

rule xxx when {
    $p : Person()
    switch( $p ) {
        case ( age < 18 &&  saving > 100 ) {
            $s : { "Rich Student" }
            break
        }
        case ( $p.age < 18 ) {
            $s : { "Student" }
        }
        case ( $p.saving > 100 ) {
            $s : { "Rich" }
        }
        case ( $p.saving < 10) {
            $s : { "Poor" }
        }
    }
}  then {
    print( $s );
}

 

If I insert new Person(){ age = 15, savings == 105), it will print a single line of "Rich Student".

 

If I insert new Person(){ age = 15, savings == 50 ), it will print a single line "Student". It will not fall through to "Rich", because it's case does not match, nor "Poor".

 

If I insert new Person(){ age = 15, savings == 5), it will print a single line "Student", and then it will print a second line "Poor".

 

 

Higher Order Queries (Query literal)

We should be able to pass a query literal as a typed parameter, and allow that query to be invoked. As per HiLog:

http://www.cs.sunysb.edu/~warren/xsbbook/node46.html

 

Query to add labeled blocks

Current queries have no block, and just an end:

query queryName(String str, int i)

end

 

This is not consistent with the new prefix labeled block approach. The "where" keyword is currently recommend.

query queryName(String str, int i) where {  //  and why not "when"?

   ...

}

 

MDP When or Where is open to debate, I felt that semantically "where" is semantically correct. Queries are passive and must be invoked by the user, so we know "when"; "when" is now. Unlike rules which are reactive, which are detecting "when".

Queries to support "then" blocks

As queries are invokable they can have both a data query and more system utility role, the later may require actions to be executed for each returned row. The "then" blocks will be be executed by the agenda and will be fired as each row is produced in the result set.

query queryName(String str, int i) where {

  ...

} then {

   ...

}

This means that the actions of the "then" block are executed with highest priority on the arrival of a new row. This violates the hitherto valid dictum that the DRL programmer can exercise absolute control over the execution order.

 

MDP salience has no bearing on this. The "then" block executes as the rows are resolved.

 

WL I've changed "salience" to priority. - Consider an insertion of MyFact. I have a high-priority rule checking its validity, immediatly retracting invalid MyFacts. But if the query is "live", it will execute the 'then' block for this MyFact, which might cause some irreversible action (e.g., by executing some global's service method.) That's why I think that all activities in DRL should observe the (hitherto) guaranteed execution ordering.

 

While Drools is currently single threaded, we hope to resolve that, so that queries be executed in a separate thread, and thus the agenda for normal rules continue to execute as normal - all in parallel. "then" blocks on queries will have the same limitations as triggers on views, you cannot mutate from the view you are selecting from.

 

WL: Does the "cannot mutate" mean that you can't use modify/retract on any of the facts bound in the 'where'? But I can still call a setter on any of the fact objects?


 

An alternative is to scrap the "query" keyword and just allow rules to specify parameters, those rules must be invoked as a query.

rule ruleName(String str, int i) when {

   ...

} then{

   ...

}

- Only consequential. - An empty pair of parentheses would distinguish a legacy rule from a query with no parameters.

 

** ET: IMHO, this whole proposal muds the differences between queries and rules, breaks best practices and will plain confuse everybody. We should not implement something just because we can. Queries return data, rules execute actions when patterns match. If one wants to execute actions for each row of a query, create a rule that calls a query on the LHS. I am strongly against this feature.

Pluggable Joins (builtins)

Allow users to write pluggable joins. Positional syntax is used for arguments.

$c : Cashflow()
log( $c; )

'log' is a user provided join. This join is passive and has no return value. It implements VoidJoin. No additonal tuple is added to the tuple chain. If we returned "null" it would signify an unseccessful join, and the LeftTuple would not propagate further. We need to expose our "unstable" join methods for this, any implementing user must ept that this is a low level plugin and subject to change.

class LogJoin implements VoidJoin { //VoidJoins have no return value
    LeftTuple assertLeftTuple(LeftTuple leftTuple, WorkingMemory wm, PropagationContext pct) {
        log( resolve( $c, leftTuple ) );
        return leftTuple;
    }
    ....
}

 

However Joins can be reactive and also return an object, such as a message join node, where the result is returned later. Returned objects are "out" variables in the arguments. The example sends $c as a message to "endpoint" and that later returns a value which is bound to $returnvalue. These nodes are async and use the WM action queue to ensure safe execution.

$c : CashFlow
msg( "endpoint", $c, $returnvalue; )

 

The Join imeplementation would look something like this. Where JoinAssertAction is a callback handler that the message broker would invoke with the returned value. Notice it returns 'null', this is because we need to wait for the reactive join "push" from the broker, once the message is received.

class MsgJoin implements Join {
    LeftTuple assertLeftTuple(LeftTuple leftTuple, WorkingMemory wm, PropagationContext pct) {
           sendTo( "endpoint", $c, new JoinAsertAction(this, leftTuple, wm, pct) );
           return null;
    }
    ....
}

 

** ET: at the moment, I don't really understand the point of adding this, so I will wait for us to mature it before I make my mind.

LHS Mutable Objects

We can borrow from OCaml's functional objects, to allow safe LHS mutation. Each mutation is actually a clone, with specified fields updated to new values. The fact class must support a specific interface or provide annotations in order to permit this feature. (To be decided later.)

 

rule xxx when {
    $c : CashFlow().{ amount = $c.amount + 1 } // selects all CashFlow facts in WM and mutates each locally

// WL --- What about CashFlow().{ amount++ } or CashFlow().{ amount += 1 } ?

    log( $c; )     //  --- WL : this is not related to LHS Mutation 
    $c.{ amount = $c.amount + 1 } // effectively clones the $c instance again, updating the "amount" field - this ensures previous joins remain integrity safe.
} then {
   println( $c );
}

if instead of println we called modify( $c ) {} it would update the $c handle to the current values - i.e. amount + 2. In the above example this would of course cause an infinite recursion, unless for instance no-loop was added.  Even though no fields were specified we infer the property mask, for property reactive.

 

WL : Given that class CachFlow is eligible for mutation/cloning, its constraints are now vulnerable to the (mostly in C) infamous typo where the omission of '=' from '==' results in a syntactically correct expression, but completely different from what was intended. @Mark: Please DON'T pshaw this away! Remember your own typo ;-)

 

Annotations

All annotations are now on the LHS and typesafe, some annotations are special and support symbols - i.e. modify, where field names do not need "fieldname" quotes.

 

@SomeAnnotation
rule xxx when {
} then {
}

 

How does this feel for @Modify, does it go before or after the pattern binding?

@Watch(name) $p : Person()

 

MF - At the moment the @watch annotation is after the pattern binding, but probably to move it before will be more clear. In general we should agree on a common convetion e.g.: all the annotations come immediatly before the element they are referred

** ET: annotations have to be after the binding, before the pattern, as bindings will work for tuples: $x : ( @ann A() and @ann B() ).

 

Annotations can be added to constraint arguments as well as to operators.

Person( name @SomeAnnotation == "mark" ) // annotation just on the operator
Person( @SomeAnnotation name == "mark" ) // annotaiton on the entire constraint

 

** ET: This is a complicated one, as the second case above could also (and would probably) be an annotation on the field name, not the whole constraint. Also, we support free form expressions, so for complex expressions, how would annotations be resolved?

Tuple Filters

Windows are actually types of tuple filters, i.e., they control what is currently propagated. The pipe symbol should be introduced, instead of 'over' and chaining should be allowed. Suspect we should revert to '.' instead of ':', too.

MyEvent() | window.time( 30s )

 

** ET: should this use the same pipe symbol that mfusco is using for functional piping: |> ?

 

For example maybe we don't need ALL events over the last 30m, instead we "sample" every 250 ms, ignoring other propagations in between that time. We keep a window open for the last 30 minutes, however we only propagate further every 10s. Throttle can be very useful with 'accumulate' to batch changes.

MyEvent() filter.sample( 250ms ) | window.time( 30m ) | filter.throttle( 10s )

 

We can also filter pattern groups. What happens here is filter the resulting B() C() chain

$a : A()
( $b : B()
  $c : C() ) | window.time( 30s ) )
$d : D()

** Still not sure what this means, or whether makes sense. Need a better example.

 

Accumulate

in 5.3 we already introduced the improved accumualte syntax with guards

accumulate( $c CashFlow(); $min : min( $c. amount ); $min > 100 )

 

We propose the small 'acc' keyword.

 

MDP - it's ergonomics, you chose when an alias "feels" suitable. In this use case it does, the length irks me, for it's use cases. It does not do so for "package" or "import". Probably becaues the later are standalone, "acc" is fully nestable.

WL --- That's your personal feeling - you did write "feels", and it irks you. I've never had such a feeling. Is there a JIRA? A rational decision would favour abbreviations for the most frequently used keywords, as e.g. "sub" in Perl - this I'd understand. 

 

Accumulate's return object, when used with 'from' is and remains the first function, regardless of whether it's bound or not.

$n : Number() from acc( $c : CashFlow(); $min : min( $c. amount ); $min > 100 )

 

 

** ET: the above is not. The return type for multiple functions is an Object[]. Also, they are 2 completely separate syntaxes to avoid this same kind of confusion. Please see the documentation, but one either uses the old "from accumulate" syntax, or uses the new short syntax with function binding and guard.

** MDP:ok, we can limit acc function chaining, to acc with only one function in it, to avoid the array issue.


However we now support type inference for that retuned value. $n and $min are effectively pointing to the same value.

$n : acc( $c : CashFlow(); $min : min( $c. amount ); $min > 100 )

 

** ET: we've been bitten in the past for allowing multiple syntaxes for the same thing just because we could. The binding on the function is clear, concise and unnambiguous. Why also add a binding to accumulate to do the same thing? this just makes language evolution harder.

 

and acc can be nested

acc( $v : acc( $c: CashFlow(); 
                        round( $c.amount ); );      // WL: Is there an optional semicolon before the closing parenthesis?
                                                                    MDP yes the last ; is optional, if no guard
         $min : min( $v ); $min > 100 ) )

 

The functional pipeing syntax can be used to chain together accumulate functions. The

acc( $c : CashFlow();
         round( $c.amount ) >| $min : min() )

** ET: we discussed this many times already and it does not make sense in my mind. Accumulate operates on "sets" of facts, not individual facts. "round()"  is a function that operates on individual values, "min()" is an accumulate function (i.e., an identifier for 4 functions) that operates on sets... it does not make sense to pipe one into the other... the correct would be:

** MDP: Had irc conversation with ET, this is now resolved. Edson thought these are normal functions; clearly that wouldn't work. Each function chained, must be a specially written function to handle streaming. Haskell has something similar, what it calls "stream fusion". Note our's must be a little more complex, as we need to support "diffs". This is clearly a feature that will benefit from collection oriented propagations too.

 

acc( $c : CashFlow(); min( round( $c.amount ) ) )

 

I honestly could not find a single case where piping makes sense for accumulate functions

 

** WL - Can you please tell me why I should use "specially written functions" to compute the minimum of all rounded amounts when I can achieve the same with out-of-the box functions? Please provide a convincing use case.

 

 

We also need to start enforcing that functions are imported, or, at least, that their type is declared.

 

XOR Conditional Element

Note that this not a logical xor, but more like the BPM xor gateway. Unlike 'or' where  each branch establishes a separate rule, 'xor' stays in a single rule, and we'll iterate each branch in turn until we find one that matches. Then the other 'xor' branches aren't evaluated any more. If no branch succeeds, 'xor' does not match.

 

A()

(xor Person( age > 35 )

       Person( age > 30 )

       Person( color == "red" )

B()

 

It'll join A then one of the 3 Persons, then B. if the first join is successful, it'll ignore the others. While patterns are shown as the direct subelements, it could be other group conditional elements.

** How do we handle 'or' inside of an xor. All 'or's are actually removed from the engine network, creating separate rules, as usual.

** Perhaps 'oneof' or 'any' would be a better name, because 'xor' as is logically incorrect.  However BPMN2 spec uses non-logical xor's, and the entire world understands xor in that context, which might be a point iv favour of 'xor'.

** ET: Some constructs do not allow for nested "OR"... e.g., inside an accumulate, no ORs allowed. Might be a similar case.


Inline Code Blocks

{....} curly brackets can be used to add code inline for LHS. That code will always execute and propagate the LeftTuple.

$p : Person()
{ log( $p ) }

 

= can be used for conditional blocks, that will only propagate depending on what was returned

$p : Person()
={ return $p.age < 30 }

 

 

blocks without '=' can use an annotation to define which propagation type they execute on: @Insert, @Update, @Delete

WL -The example has '@Retract' - not @Delete?     NOTE -----------------------------------------------------------------------------^^^^^^^^?

-----------------------------vvvvvvvv    

$p : Person()
@Insert @Update @Retract { log( $p ) } 

 

Blocks can also be fed into patterns:

Person() <| { new Person() }

 

MF - Is this equivalent to a "more powerful from"? If so why don't we just use the same keyword and write "Person() from { new Person() }" instead?

 

This can allow our functional programming to be chained inside of the block and then that result fed into the pattern:

Person() <| { ... functional chains here .... }

** mfusco to flesh out

 

List element and Sublist Selection

list[0] // first element
list[0..5] returns sublist of 0..5 elements
list[4..n] returns sublist of 4th to last elements
list[n] return the last element

** What about skipping? list[0..n..2] 0..n but skip every 2 elements.

WL: What if I have bound variable 'n' to 42? Will 'n' still mean the "last index"?

WL: Why not use -1 for last, -2 for last but one, etc?

 

This will allow list comprehension

query iterateList(List list) {
    println( list[0]; )
    iterateList( list[1..n]; )
}

 

Need to make sure this works well with range generate syntax

 

Range Generation

Generates a list of 1 to 5.

acc( $n : { [1, 2, 3, 4, 5] };
         avg( $n ) )

 

generates 1, 2, 3 all the way up to 1000

acc( $n : { [1:1000] };
        avg( $n ); )

 

generates 0, 5, 10 in steps of 5 all the way up to  1000

acc( $n : { [0:1000:5] };
        avg( $n ); )

 

We'll also support Iterable function implementations with yelds.

 

WL --- "yelds" --> "yields"? What does it mean?

 

Block Execution Time

By default any inline blocks {....} are executed at the time of propagation, any then/break blocks are executed as Activations on the agenda.

 

 

@Agenda(true/false) can be used to alter the default behaviour

 

WL --- Now I see how sneaky this is: you write one thing and then use an @nnotation to invert its semantics to something you could have cleanly by using the other thing in the first place. Distasteful, really.

 

** ET: I am also not sure about this.... need more time to think about it.

 

MF - I am not sure of which could be the effects of letting a consequence or a code block to bypass the agenda in this way. Won't that leave the working memory in an inconsistent state? I am also afraid that people will start misusing it, basically employing it as a "salience on steroids".

 

MF - I don't like to have an annotation with a boolean arg. I think it is more clear to have 2 distinct annotation in this case like we did with @PropertyReactive/@ClassReactive. I am also not sure that give the possibility to override a default on a level and change it again in a more nested level is very readable.


rule xxx when {
    A()
    @Agenda(false) then[1]
    B()
    @Agenda(true){  log("hello")    }
} then {
   ...
} then[1] {
  ...
}

 

To change the behaviour of the "default" then block, place the @Agenda activation on the rule itself - this will then be true for all labeled blocks, unless overidden locally.

@Agenda(false)
rule xxx when {
    A()
    @Agenda(false) then[1]
    B()
    @Agenda {  log("hello")    } //default no arguments is "true"
} then {
   ...
} then[1] {
  ...
}

 

@Async allows for the firing rule to handled asynchronously via a thread worker pool

@Async @Agenda(false)
rule xxx when {
    A()
    @Async @Agenda(false) then[1]
    B()
    @Async @Agenda {  log("hello")    } // default no arguments is "true"
} then {
   ...
} then[1] {
  ...
}

 

MF - What's the purpose of having something to be triggered by the agenda but running asynchronously?

Event Sequencing

See https://community.jboss.org/wiki/EventSequencing

 

Type Safe Queries

Our block language must support type safe queries, similar to JPA2. Further those queries statements must be able to support unbound arguments, i.e. Variable.

 

Anonymous LHS

Our block language should be able to declare a nested LHS, so that it can be invoked like a query and passed around like a query literal.

 

TODO

  • List of ideas not fleshed out enough to include, but hope to come back to later.
  • Rising/Falling edges
  • Field versioning
  • Consume
  • Single Pass insertions
  • Unmatch callback
  • Relative Salience
  • Annotation for group selections, see RuleModule Groups
  • RuleModule groups, along with function literans attach to events
  • SQL operations, distinct/group by
  • Escape dialects
  • Otherwise
  • None blocking exists/not nodes
  • How ML O-DL fits into all of this