Until now Drools rules have been always expressed in the form:
-- updated --
To clarify some of comments. Here is an if..do rule, from above
That rule is the equivalent of the two single rules:
The same rule but a 'if..break' instead:
Notice the negation of the eval:
If we take the last, more complex rule:
That translates into the following three rules. Notice for the free parking I explicitly add in all the negations that are implicit to the above rule.
rule "name" when LHS (conditional element) then RHS (consequence) endSometimes this could be somewhat limiting and leads to verbose and difficult to be maintained repetitions like in the following example:
rule "Give 10% discount to customers older than 60" when $customer : Customer( age > 60 ) then modify($customer) { setDiscount( 0.1 ) }; end rule "Give free parking to customers older than 60" when $customer : Customer( age > 60 ) $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; endIt is already possible to partially overcome this problem by making the second rule extending the first one like in:
rule "Give 10% discount to customers older than 60" when $customer : Customer( age > 60 ) then modify($customer) { setDiscount( 0.1 ) }; end rule "Give free parking to customers older than 60" extends "Give 10% discount to customers older than 60" when $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; endAnyway, starting from Drools 5.5, it is possible to define more labelled consequences other than the default one in a single rule, so, for example, the 2 former rules can be compacted in only one like it follows:
rule "Give 10% discount and free parking to customers older than 60" when $customer : Customer( age > 60 ) do[giveDiscount] $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; then[giveDiscount] modify($customer) { setDiscount( 0.1 ) }; endThis last rule has 2 consequences, the usual default one, plus another one named "giveDiscount" that is activated, using the keyword do, as soon as a customer older than 60 is found in the knowledge base, regardless of the fact that he owns a car or not. The activation of a named consequence can be also guarded by an additional condition like in this further example:
rule "Give free parking to customers older than 60 and 10% discount to golden ones among them" when $customer : Customer( age > 60 ) if ( type == "Golden" ) do[giveDiscount] $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; then[giveDiscount] modify($customer) { setDiscount( 0.1 ) }; endThe condition in the if statement is always evaluated on the pattern immediately preceding it. In the end this last, a bit more complicated, example shows how it is possible to switch over different conditions using a nested if/else statement:
rule "Give free parking and 10% discount to over 60 Golden customer and 5% to Silver ones" when $customer : Customer( age > 60 ) if ( type == "Golden" ) do[giveDiscount10] else if ( type == "Silver" ) break[giveDiscount5] $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; then[giveDiscount10] modify($customer) { setDiscount( 0.1 ) }; then[giveDiscount5] modify($customer) { setDiscount( 0.05 ) }; endHere I wanted to give a 10% discount AND a free parking to Golden customers over 60, but only a 5% discount (without free parking) to the Silver ones. I achieved this result by activating the consequence named "giveDiscount5" using the keyword break instead of do. In fact do just schedules a consequence in the agenda, allowing the remaining part of the LHS to continue of being evaluated as per normal, while break also blocks any further pattern matching evaluation. Note, of course, that the activation of a named consequence not guarded by any condition with break doesn't make sense (and generates a compile time error) since otherwise the LHS part following it would be never reachable.
-- updated --
To clarify some of comments. Here is an if..do rule, from above
rule "Give free parking to customers older than 60 and 10% discount to golden ones among them" when $customer : Customer( age > 60 ) if ( type == "Golden" ) do[giveDiscount] $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; then[giveDiscount] modify($customer) { setDiscount( 0.1 ) }; end
That rule is the equivalent of the two single rules:
rule "Give free parking to customers older than 60 and 10% discount to golden ones among them" when $customer : Customer( age > 60 ) $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; end rule "Give free parking to customers older than 60 and 10% discount to golden ones among them - 1" when $customer : Customer( age > 60 ) eval ( $customer.type == "Golden" ) then modify($customer) { setDiscount( 0.1 ) }; end
The same rule but a 'if..break' instead:
rule "Give free parking to customers older than 60 and 10% discount to golden ones among them - 2" when $customer : Customer( age > 60 ) if ( type == "Golden" ) break[giveDiscount] $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; then[giveDiscount] modify($customer) { setDiscount( 0.1 ) }; end
Notice the negation of the eval:
rule "Give free parking to customers older than 60 and 10% discount to golden ones among them - 1" when $customer : Customer( age > 60 ) eval ( ! ($customer.type == "Golden") ) $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; end rule "Give free parking to customers older than 60 and 10% discount to golden ones among them - 2" when $customer : Customer( age > 60 ) eval ( $customer.type == "Golden" ) then modify($customer) { setDiscount( 0.1 ) }; end
If we take the last, more complex rule:
rule "Give free parking and 10% discount to over 60 Golden customer and 5% to Silver ones" when $customer : Customer( age > 60 ) if ( type == "Golden" ) do[giveDiscount10] else if ( type == "Silver" ) break[giveDiscount5] $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; then[giveDiscount10] modify($customer) { setDiscount( 0.1 ) }; then[giveDiscount5] modify($customer) { setDiscount( 0.05 ) }; end
That translates into the following three rules. Notice for the free parking I explicitly add in all the negations that are implicit to the above rule.
rule "Give free parking and 10% discount to over 60 Golden customer and 5% to Silver ones - 1" when $customer : Customer( age > 60 ) eval( $customer.type == "Golden" ) then modify($customer) { setDiscount( 0.1 ) }; end rule "Give free parking and 10% discount to over 60 Golden customer and 5% to Silver ones - 2" when $customer : Customer( age > 60 ) eval( !( $customer.type == "Golden" ) && $customer.type == "Silver" ) then modify($customer) { setDiscount( 0.05 ) }; end rule "Give free parking and 10% discount to over 60 Golden customer and 5% to Silver ones - 3" when $customer : Customer( age > 60 ) eval( ( $customer.type == "Golden" ) || ( !( $customer.type == "Golden" ) && !( $customer.type == "Silver" ) ) ) ) $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; endWe have further work coming down the line, that will allow nested patterns inside of the branch's. You can see an early draft proposal of this here: https://community.jboss.org/wiki/BranchCEs

Just to clarify Mario, with the use of do[label] the default unlabelled consequence is always ran by default; whereas with break[label] the default unlabelled consequence is not ran?
ReplyDeleteAlso do then[label] sections drop through? e.g. given the following and a Golden Customer older than 60 do they get 10% or 15% discount?
when
$c : Customer( age > 60 )
if( type == "Golden" ) do[increaseDiscountBy10]
if( type == "Silver" ) do[increaseDiscountBy5]
then
//Do nothing by default
then[increaseDiscountBy10]
modify( $c ) { increaseDiscountBy( 0.10 ) };
then[increaseDiscountBy5]
modify( $c ) { increaseDiscountBy( 0.05 ) };
end
Great stuff.
Mike
I second the question. :)
ReplyDeleteIf the default consequence is not always run then the multi-consequence rule does not have identical functionality to it's multiple rule equivalent. Not necessarily a bad thing. But if inserting a break changes the functionality unexpectedly then that will lead to confusion.
"Just to clarify Mario, with the use of do[label] the default unlabelled consequence is always ran by default; "
ReplyDeleteThe default is reached, when the rule is fully matched to the last pattern. It's like an implicit do[default] as the last element.
It's a bit more advanced than what is shown here, as it supports nested patterns in conditional blocks. But it shows how they translate to traditional rules. This might help in understanding:
https://community.jboss.org/wiki/BranchCEs
if( type == "Golden" ) do[increaseDiscountBy10]
ReplyDeleteif( type == "Silver" ) do[increaseDiscountBy5]
In example above it's mutually exclusive. But lets try this:
if( age > 70 ) do[increaseDiscountBy10]
if( age > 60 ) do[increaseDiscountBy5]
As there is no 'else' to separate the two 'if' statements, if the person was over 70 - they would have both discounts applied. If there was an 'else' only one discount would be provided.
Good catch (on the mutual exclusivity) and clear answer. Thanks.
ReplyDeleteI've just done another update, showing the more complex if/else if rule expanded.
ReplyDelete