As promised :) (be carefull what you wish for)
- "<init code>: this is a semantic block of code in the selected dialect
that will be executed once for each tuple, before iterating over the
source objects."
What is a "tuple"?
Do you mean "once for each rule" or "once for each pattern match"?
(I presume the first, after reading further.)
- "Source objects" are facts that follow the source pattern I presume?
Or is there a difference?
- The method javadocs in the AverageAccumulateFunction example are a bit
too besides the point IMHO. I'd leave them out.
Summary: nothing seriously wrong with that documentation, looks good :)
With kind regards,
Geoffrey De Smet
Edson Tirelli wrote:
Geoffrey,
The "sum" function that is shipped with drools always uses double
internally, but all numeric accumulate functions must have return type
Number to avoid class cast exceptions... I must write that in the
docs... so, recommendation is to do:
Number( $total : intValue ) from accumulate(
Hop($distance : distance ), // distance is an int
sum($distance)
);
Now, having said that, the decision to use double is because we
needed a one size fits all to ship with drools. On the other hand I
tried to make as simple as possible to plug new or replace built-in
functions. So, if you want a sum function that uses only integers, you
can easily develop and plug it your own. Since I just wrote the docs on
how to do it, may I ask you please to read them and provide a feedback
if they are good, need more info, shall I write them in another way, etc?
BTW, when I say it is really simple to create another sum function,
I mean, you can do it in about 20 minutes. Really!
Find attached the HTML doc page I'm talking about.
[]s
Edson
2007/7/22, Geoffrey De Smet < ge0ffrey.spam(a)gmail.com
<mailto:ge0ffrey.spam@gmail.com>>:
Hi guys,
I finally got around to experimenting with the accumulate support in
LocalSearchSolver. Thanks for implementing it :)
I use nothing but int's in my calculations,
but I get a ClassCastExceptions, as sum() always returns a Double and
setSoftConstraintsBroken(int) failes.
In a previous benchmarks I 've proven that summing integers in double's
instead of int's hurts performance for 10% or more (which is rather
big).
Is there a sumInteger() available (or do I need to write it)? Or can it
be overloaded over sum() in a much cleaner way?
rule "ScoreCalculator"
when
$scoreFact : ScoreFact(); // singleton
$total : Integer() from accumulate(
Hop($distance : distance ), // distance is an int
sum($distance)
);
then
$scoreFact.setSoftConstraintsBroken($total);
end
svn is still here, till it's ready for drools:
https://taseree.svn.sourceforge.net/svnroot/taseree/trunk
--
With kind regards,
Geoffrey De Smet
_______________________________________________
rules-dev mailing list
rules-dev(a)lists.jboss.org <mailto:rules-dev@lists.jboss.org>
https://lists.jboss.org/mailman/listinfo/rules-dev
--
Edson Tirelli
Software Engineer - JBoss Rules Core Developer
Office: +55 11 3529-6000
Mobile: +55 11 9287-5646
JBoss, a division of Red Hat @
www.jboss.com <
http://www.jboss.com>
------------------------------------------------------------------------
3.6. Advanced Conditional Elements
Prev <ch03s05.html> Chapter 3. The Rule Language Next <ch03s07.html>
------------------------------------------------------------------------
3.6. Advanced Conditional Elements
Note
/|(updated to Drools 4.0)|/
Drools 4.0 introduces a whole new set of conditional elements in order
to support full First Order Logic expressiveness, as well as some
facilities for handling collections of facts. This section will detail
the following new Conditional Elements:
*
from
*
collect
*
accumulate
*
forall
3.6.1. From
The *from* Conditional Element allows users to specify a source for
patterns to reason over. This allows the engine to reason over data not
in the Working Memory. This could be a sub-field on a bound variable or
the results of a method call. It is a powerful construction that allows
out of the box integration with other application components and
frameworks. One common example is the integration with data retrieved
on-demand from databases using hibernate named queries.
The expression used to define the object source is any expression that
follows regular MVEL syntax. I.e., it allows you to easily use object
property navigation, execute method calls and access maps and
collections elements.
Here is a simple example of reasoning and binding on another pattern
sub-field:
rule "validate zipcode"
when
Person( $personAddress : address )
Address( zipcode == "23920W") from $personAddress
then
# zip code is ok
end
With all the flexibility from the new expressiveness in the Drools
engine you can slice and dice this problem many ways. This is the same
but shows how you can use a graph notation with the 'from':
rule "validate zipcode"
when
$p : Person( )
$a : Address( zipcode == "23920W") from $p.address
then
# zip code is ok
end
Previous examples were reasoning over a single pattern. The *from* CE
also support object sources that return a collection of objects. In that
case, *from* will iterate over all objects in the collection and try to
match each of them individually. For instance, if we want a rule that
applies 10% discount to each item in an order, we could do:
rule "apply 10% discount to all items over US$ 100,00 in an order"
when
$order : Order()
$item : OrderItem( value > 100 ) from $order.items
then
# apply discount to $item
end
The above example will cause the rule to fire once for each item whose
value is greater than 100 for each given order.
The next example shows how we can reason over the results of a hibernate
query. The Restaurant pattern will reason over and bind with each result
in turn:
3.6.2. Collect
The *collect* Conditional Element allows rules to reason over collection
of objects collected from the given source or from the working memory. A
simple example:
import java.util.ArrayList
rule "Raise priority if system has more than 3 pending alarms"
when
$system : System()
$alarms : ArrayList( size >= 3 )
from collect( Alarm( system == $system, status == 'pending' ) )
then
# Raise priority, because system $system has
# 3 or more alarms pending. The pending alarms
# are $alarms.
end
In the above example, the rule will look for all pending alarms in the
working memory for each given system and group them in ArrayLists. If 3
or more alarms are found for a given system, the rule will fire.
The *collect* CE result pattern can be any concrete class that
implements tha java.util.Collection interface and provides a default
no-arg public constructor. I.e., you can use default java collections
like ArrayList, LinkedList, HashSet, etc, or your own class, as long as
it implements the java.util.Collection interface and provide a default
no-arg public constructor.
Both source and result patterns can be constrained as any other pattern.
Variables bound before the *collect* CE are in the scope of both source
and result patterns and as so, you can use them to constrain both your
source and result patterns. Although, the /collect( ... )/ is a scope
delimiter for bindings, meaning that any binding made inside of it, is
not available for use outside of it.
Collect accepts nested *from* elements, so the following example is a
valid use of *collect*:
import java.util.LinkedList;
rule "Send a message to all mothers"
when
$town : Town( name == 'Paris' )
$mothers : LinkedList()
from collect( Person( gender == 'F', children > 0 )
from $town.getPeople()
)
then
# send a message to all mothers
end
3.6.3. Accumulate
The *accumulate* Conditional Element is a more flexible and powerful
form of *collect* Conditional Element, in the sense that it can be used
to do what *collect* CE does and also do things that *collect* CE is not
capable to do. Basically what it does is it allows a rule to iterate
over a collection of objects, executing custom actions for each of the
elements, and at the end return a result object.
The general syntax of the *accumulate* CE is:
/|<result pattern>|/ from accumulate( /|<source pattern>|/,
init( /|<init code>|/ ),
action( /|<action code>|/ ),
reverse( /|<reverse code>|/ ),
result( /|<result expression>|/ ) )
The meaning of each of the elements is the following:
*
*<source pattern>*: the source pattern is a regular pattern that
the engine will try to match against each of the source objects.
*
*<init code>*: this is a semantic block of code in the selected
dialect that will be executed once for each tuple, before
iterating over the source objects.
*
*<action code>*: this is a semantic block of code in the selected
dialect that will be executed for each of the source objects.
*
*<reverse code>*: this is an optional semantic block of code in
the selected dialect that if present will be executed for each
source object that no longer matches the source pattern. The
objective of this code block is to "undo" any calculation done in
the <action code> block, so that the engine can do decremental
calculation when a source object is modified or retracted, hugely
improving performance of these operations.
*
*<result expression>*: this is a semantic expression in the
selected dialect that is executed after all source objects are
iterated.
*
*<result pattern>*: this is a regular pattern that the engine
tries to match against the object returned from the <result
expression>. If it matches, the *accumulate* conditional element
evaluates to *true* and the engine proceeds with the evaluation of
the next CE in the rule. If it does not matches, the *accumulate*
CE evaluates to *false* and the engine stops evaluating CEs for
that rule.
It is easier to understand if we look at an example:
rule "Apply 10% discount to orders over US$ 100,00"
when
$order : Order()
$total : Number( doubleValue > 100 )
from accumulate( OrderItem( order == $order, $value : value ),
init( double total = 0; ),
action( total += $value; ),
reverse( total -= $value; ),
result( total ) )
then
# apply discount to $order
end
In the above example, for each Order() in the working memory, the engine
will execute the *init code* initializing the total variable to zero.
Then it will iterate over all OrderItem() objects for that order,
executing the *action* for each one (in the example, it will sum the
value of all items into the total variable). After iterating over all
OrderItem, it will return the value corresponding to the *result
expression* (in the above example, the value of the total variable).
Finally, the engine will try to match the result with the Number()
pattern and if the double value is greater than 100, the rule will fire.
The example used java as the semantic dialect, and as such, note that
the usage of ';' is mandatory in the init, action and reverse code
blocks. The result is an expression and as such, it does not admit ';'.
If the user uses any other dialect, he must comply to that dialect
specific syntax.
As mentioned before, the *reverse code* is optional, but it is strongly
recommended that the user writes it in order to benefit from the
/improved performance on update and retracts/.
The *accumulate* CE can be used to execute any action on source objects.
The following example instantiates and populates a custom object:
rule "Accumulate using custom objects"
when
$person : Person( $likes : likes )
$cheesery : Cheesery( totalAmount > 100 )
from accumulate( $cheese : Cheese( type == $likes ),
init( Cheesery cheesery = new Cheesery(); ),
action( cheesery.addCheese( $cheese ); ),
reverse( cheesery.removeCheese( $cheese ); ),
result( cheesery ) );
then
// do something
end
3.6.3.1. Accumulate Functions
The accumulate CE is a very powerful CE, but it gets real declarative
and easy to use when using predefined functions that are known as
Accumulate Functions. They work exactly like accumulate, but instead of
explicitly writing custom code in every accumulate CE, the user can use
predefined code for common operations.
For instance, the rule to apply discount on orders written in the
previous section, could be written in the following way, using
Accumulate Functions:
rule "Apply 10% discount to orders over US$ 100,00"
when
$order : Order()
$total : Number( doubleValue > 100 )
from accumulate( OrderItem( order == $order, $value : value ),
sum( $value ) )
then
# apply discount to $order
end
In the above example, sum is an AccumulateFunction and will sum the
$value of all OrderItems and return the result.
Drools 4.0 ships with the following built in accumulate functions:
*
average
*
min
*
max
*
count
*
sum
These common functions accept any expression as input. For instance, if
someone wants to calculate the average profit on all items of an order,
a rule could be written using the average function:
rule "Average profit"
when
$order : Order()
$profit : Number()
from accumulate( OrderItem( order == $order, $cost : cost, $price : price
)
average( 1 - $cost / $price ) )
then
# average profit for $order is $profit
end
Accumulate Functions are all pluggable. That means that if needed,
custom, domain specific functions can easily be added to the engine and
rules can start to use them without any restrictions. To implement a new
Accumulate Functions all one needs to do is to create a java class that
implements the org.drools.base.acumulators.AccumulateFunction interface
and add a line to the configuration file or set a system property to let
the engine know about the new function. As an example of an Accumulate
Function implementation, the following is the implementation of the
"average" function:
/*
* Copyright 2007 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Created on Jun 21, 2007
*/
package org.drools.base.accumulators;
/**
* An implementation of an accumulator capable of calculating average values
*
* @author etirelli
*
*/
public class AverageAccumulateFunction implements AccumulateFunction {
protected static class AverageData {
public int count = 0;
public double total = 0;
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#createContext()
*/
public Object createContext() {
return new AverageData();
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#init(java.lang.Object)
*/
public void init(Object context) throws Exception {
AverageData data = (AverageData) context;
data.count = 0;
data.total = 0;
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#accumulate(java.lang.Object,
java.lang.Object)
*/
public void accumulate(Object context,
Object value) {
AverageData data = (AverageData) context;
data.count++;
data.total += ((Number) value).doubleValue();
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#reverse(java.lang.Object,
java.lang.Object)
*/
public void reverse(Object context,
Object value) throws Exception {
AverageData data = (AverageData) context;
data.count--;
data.total -= ((Number) value).doubleValue();
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#getResult(java.lang.Object)
*/
public Object getResult(Object context) throws Exception {
AverageData data = (AverageData) context;
return new Double( data.count == 0 ? 0 : data.total / data.count );
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#supportsReverse()
*/
public boolean supportsReverse() {
return true;
}
}
The code for the function is very simple, as we could expect, as all the
"dirty" integration work is done by the engine. Finally, to plug the
function into the engine, we added it to the configuration file:
drools.accumulate.function.average =
org.drools.base.accumulators.AverageAccumulateFunction
Where "drools.accumulate.function." is a prefix that must always be
used, "average" is how the function will be used in the rule file, and
"org.drools.base.accumulators.AverageAccumulateFunction" is the fully
qualified name of the class that implements the function behavior.
3.6.4. Forall
*Forall* is the Conditional Element that completes the First Order Logic
support in Drools. The syntax is very simple:
forall( /|<select pattern>|/ /|<constraint patterns>|/ )
The *forall* Conditional Element will evaluate to true when all facts
that match the /|<select pattern>|/ match all the /|<constraint
patterns>|/. Example:
rule "All english buses are red"
when
forall( $bus : Bus( type == 'english')
Bus( this == $bus, color = 'red' ) )
then
# all english buses are red
end
In the above rule, we "select" all Bus object whose type is
"english".
Then, for each fact that matchs this pattern we evaluate the following
patterns and if they match, the forall CE will evaluate to true. Another
example:
rule "all employees have health and dental care programs"
when
forall( $emp : Employee()
HealthCare( employee == $emp )
DentalCare( employee == $emp )
)
then
# all employees have health and dental care
end
Forall can be nested inside other CEs for complete expressiveness. For
instance, *forall* can be used inside a *not* CE:
rule "not all employees have health and dental care"
when
not forall( $emp : Employee()
HealthCare( employee == $emp )
DentalCare( employee == $emp )
)
then
# not all employees have health and dental care
end
As a side note, forall Conditional Element is equivalent to writing:
not( /|<select pattern>|/ and not ( and /|<constraint patterns>|/ ) )
Also, it is important to note that *forall is a scope delimiter*, so it
can use any previously bound variable, but no variable bound inside it
will be available to use outside of it.
------------------------------------------------------------------------
Prev <ch03s05.html> Up <ch03.html> Next <ch03s07.html>
3.5. Rule Home <title.html> | ToC <bk01-toc.html> 3.7. Query
------------------------------------------------------------------------
_______________________________________________
rules-dev mailing list
rules-dev(a)lists.jboss.org
https://lists.jboss.org/mailman/listinfo/rules-dev