I had an interesting discussion today, and one of the topics was operator usability: even if the 4 steps described in the previous blog (http://blog.athico.com/2009/05/imperfect-evaluations.html) allow to define a custom evaluator quickly, will a user have to repeat them for every custom operator/evaluator they want to use? The answer is no…
- Many Operators, One Definition, Many Evaluators
A user may want to define different, but correlated evaluators. Just to make an example, consider an Operator (“white”) and its negated (e.g. “black”). Or think of the fuzzy sets that could be used to define a domain (e.g. “very young”, “young”, “adult”, “old”, “very old” for ages). One still needs different Evaluators, but can use just one Definition class. So, the steps become:
- Attach the Operators to the same Definition:
org.drools.evaluator.[eval1] = [MyMultipleEvaluatorDefinition]
Notice (thanks Edson) that this needs to be done only once. - [MyMultipleEvaluatorDefinition] must register the different Operators. Also take care that getEvaluatorIds() returns all the ids.
- The method
getEvaluator(..., String operatorId, ...)
must return the appropriate Evaluator according to the value of operatorId. Maybe a
Map<String,Evaluator>
could be useful…
- Of course, the individual Evaluators have to be defined independently
- Many Operators, One Definition, One Evaluator
A user could otherwise have an object capable of evaluating different properties. Suppose, for example, you have a Neural Network trained for Optical Character Recognition.
For the unfamiliar readers, such a NN is a classifier which processes a bitmap image and returns the character which most resembles – in the sense of probability or similarity – the shape in the image.
Whatever the case, the Evaluator, e.g. the network, has more than one imperfect outputs, each one with a different symbolic label.
A way to use it could be to define an Operator, e.g., “resembles” and attach the network to it, writing rules such as:
Bitmap( this ~resembles 'A' )
But imagine that one wants to define different Operators:
Bitmap( this ~isA )
...
Bitmap( this ~isZ )
To do this, one would have to behave exactly as in the previous case, but now the Evaluators would be different Adapters wrapping the single “real” Evaluator, such as:
public class NNAdapter extends BaseImperfectEvaluator {
private char target;
private static NeuralNetwork classifier;
}
Notice that this, independently on the chosen solution, is an option for the easy creation of hybrid intelligent systems, mapping a connectionist module such as a Neural Network onto a symbolic structure such as “resembles” or “isX”. This is not trivial.
- Dynamic Evaluators
Surely there exist more patterns, so let me introduce more. The Evaluators defined so far are static, since they are to be known at compile-time. To use a custom Evaluator at run-time, two dynamic (meta) Evaluators have been defined:
- Seems
Consider:
Person( $a : age ~seems old )
The meta-evaluator SeemsEvaluator, associated to the Operator “seems”, extracts an object from the left field and an unary Evaluator from the right expression, according to the pattern:
Type( $f : field ~seems eval )
To return a degree, SeemsEvaluator invokes the right Evaluator on the left field.
Having to return an Evaluator, the right argument is typically a variable or an expression, but can’t be a literal constant. However, there is an exception: it can be a String, but in that case the field’s value is required to be an object implementing an interface, IDynamicEvaluable, with the single method:
getEvaluator(String args)
This is a callback to the evaluand, which provides the appropriate Evaluator itself.
(A final remark: I know, you would have expected the SeemsEvaluator to be attached to the Operator “is”. But “seems”, imho, recalls more its imperfect nature. If you don’t like it, just attach the Evaluator to a different Operator.)
2. Evaluates
“Seems” has a dual Operator/Evaluator: “Evaluates”. It allows to write rules using the pattern:
Type( $f : evaluator ~evaluates value )
which includes
Evaluator( this ~evaluates value )
The (unary) Evaluator must be the pattern object itself, or can be stored in a field.
Again, “evaluates” feeds the result of the right expression to the left Evaluator and returns the result.
— thanks to Edson Tirelli for some precious advice on how to improve this post!