I can see the risk of bad practises - your example with a conditional in a function is indeed something that should make people nervous.
I use functions for a few patterns:
1) Debug assistance. For example
function void retractTrigger( KnowledgeHelper drools, Trigger trigger )
{
drools.retract( trigger );
Debug.log( "Retracted %s\n", trigger );
}
in this sort of situation, a function gives me two things. It removes the need to keep writing the same pattern again and again. And it gives me a single enable/disable point for the debug message, making it a small action to reliably turn all debug of this type on/off without hunting over multiple files. (BTW personally I much prefer retract over delete, although I do note the documentation says retract is now deprecated in favour of delete. So many things have methods called delete, whereas retract (to me) stands out as specifically being drools).
2) Simple effort reduction - basic macro behaviour
function void throwRuntimeException(KnowledgeHelper drools, Object obj)
{
throw new RuntimeException( "FAILURE: " + drools.getRule().getName() + ": " + obj);
}
Having a single function means my convention on presentation is contained in only one place.
3) Similar to your "multiple rules need, in practice, to execute the same conclusion"
function Consumer newConsumer( KnowledgeHelper drools, Technique t, boolean inside, String name, ZOM zomness, Class clazz )
{
Consumer c = new Consumer( t, inside, name );
drools.insert( c );
drools.insert( new ZOMConstraint( c, zomness ) );
drools.insert( new ClassConstraint( c, clazz ) );
return c;
}
This shape of usage is where multiple facts need asserting into the WS as a result of the one action. Writing that sort of sequence out each time is error prone, time consuming and tedious if, for example, a third supporting fact needs inserting.
In some situations your approach of inserting some form of trigger fact is an appropriate structure, I agree. And indeed I have similar approaches in places. Sometimes the OR approach is reasonable. But sometimes the different conditions are many and spread across files and pulling them together into one rule wouldn't feel a good shape of outcome.
4) Usage of modify
The reason for calling out modify is when a state change is needed. For example
function void actionCore( KnowledgeHelper drools, Technique action, TechniqueState ts )
{
trace( drools );
Provider primary_i = newProvider( drools, action, true, "primary_i", ZOM.MUST, FoodItem.class );
Consumer yield_i = newConsumer( drools, action, true, "yield_i", ZOM.MUST, CompositeIngredient.class );
drools.insert( new ProtoBoundary3p( action, "primary", primary_i ) );
drools.insert( new ProtoBoundary3c( yield_i, action, "yield" ) );
drools.insert( new Joiner( primary_i, yield_i ) );
}
rule "represent action clean"
when
$ts: TechniqueState( $action, State.ROMEO; $action instanceof Action, $action#Action.name == "clean" )
TechniqueConstraintsSatisfied( $action; )
then
actionCore( drools, $action, $ts );
modify( $ts ) { setState( State.PAPA ) }
end
Ideally for this shape I would want the state modifier in the function as well.
In summary, there are a range of code shapes where functions offer the hope of writing something once rather than many times, and offer simplicity if the (otherwise repetitive sequence) needs to be changed or tweaked in the future.
I guess it's another example of a classic dilemma - how to provide flexibility whilst discouraging "bad things".
Borris