[rules-dev] A syntax for rules
Wolfgang Laun
wolfgang.laun at gmail.com
Wed Jul 25 04:00:05 EDT 2012
-------------- next part --------------
It took me a while to resolve my timidity and start this email. It's about a more or less different approach to designing a syntax for rules. One guideline was to avoid a mutitude of syntactic constructs - I tried to get by with a minimum of phrases. Some requirements I kept in mind:
* It must be possible to dynamically permit and forbid CE joining
by conditional expressions.
* It must be possible to continue CE joining via more than one way over
the CEs present in a single LHS.
* CE joining may terminate, and lead to an activation, at more than one point
in the the several ways over the CEs of a single LHS, where each termination
has its own consequence.
As I understand the LHS part of a rule, it's about computing one or more Booleans guarding the execution of RHS code, more than one Boolean for a more sophisticated structure, with RHS code being in more than one place. In addition to determining these Booleans it must also be possible to pick up (i.e., bind a variable to) participating objects, their fields and "ad-hoc" results. Basically there are several kinds of value producing elements (VPEs): Patterns, Accumulators, Evaluations. And then there are "outer level" Operators (OLOs) for combining these elements.
One guideline in the syntax design is to distinguish between OLOs and producing elements. Hence, the OLOs are
['when'|'and'] Block
'or' Block
'xor' Block
In addition to the RHS
'then' Block
all Blocks have the same enclosing pair of braces ({...}) and contain a sequence of VPEs or OLOs and RHSs.
There is a simple VPE producing a Boolean:
'is' '(' BooleanExpression ')'
Patterns combine a type and a parenthesized sequence of (optional) positional bindings and (optional) constraints. (Nothing new here.) Rules may contain repeated alternations of an OLO and a "then" block, and such blocks may be placed after any VPE. (This means that 'nand' and 'nor' blocks might be useful, too - if only to detect where a match fails.)
rule ex1 when {
$p: Person( name == "Joe" )
} then {
System.out.print( $p );
} xor {
$c: Car( owner == $p ) then { System.out.println( " owns " + $c ); }
is() then { System.out.println( " has no car" ); } // is() == true
} and {
Person( $name: name, spouse == $p )
} then {
System.out.println( "Is married to " + $name );
}
A variable bound to an object can be used to force identity in a subsequent pattern, possibly combined with a subclass test. If given with no type, it just recalls the bound object. If followed by a pipe, it constructs a "local fact". (Hitherto: from.)
rule ex2 when {
$p: Person( name == "Joe", $a: address )
or {
$p: Teacher( subjects contains Subject.MATH ) then {
System.out.println( "Joe teaches MATH" );
}
$p: Student( subjects contains Subject.MATH ) then {
System.out.println( "Joe learns MATH" );
}
$p ( $occ: occupation != null ) then { // "no type" is "type when bound"
System.out.println( "Joe is a " + $occ );
}
is( $p.age > 16 ) then {
System.out.println( "Joe is an idle loafer!" );
}
} and {
$a | Address( state == "CA" )
} then {
System.out.println( "Lives in California!" );
}
Any block may be preceded by a VPE, which acts as a guard, i.e., the block is entered only when there is a match to a fact or a true result.
The VPE 'let' evaluates and returns any expression; it's truth value being true by definition. It makes only sense when combined with a bound variable. If the expression returns a collection, it succeeds repeatedly (or-ish).
rule ex3 { // note that 'and'/'when' can be omitted
$subject: let( EnumSet.allOf( Subject.class ) )
accumulate( $t: Teacher( subjects contains $subject );
$teachers: asList( $t.name ); $c > 0 )
} then {
System.out.println( "Subject " + $subject + " taught by " + $teachers );
}
You may hove noticed that the patterns x{...} and y(...) are used for separate syntactic categories. This improves "orientation quality" in the code.
Another example:
rule ex11 when {
$car: Car( $owner: owner )
} then {
System.out.print( $car );
} or {
Person( this == $owner )
then { System.out.print( " owned by " + $owner ); }
not Person( this == $owner )
then { System.out.print( " ownerless" ); }
} then {
System.out.println(); // just a LF
} when {
$tv: TrafficViolation( car == $car )
} then {
System.out.println( "traffic violation " + $tv );
}
A multiway continuation with the first matching branch is written using "xor".
rule ex12 when {
$p: Person()
Car( owner == $p )
xor {
{ $p( name == "John" ) $b: let( new B(1) ) }
{ $p: Worker( name == "john" ) $b: let( new B(2) ) }
{ $b: let ( new B(3) ) }
}
A( field == $b )
} then {
}
Since not() and exists() result in a Boolean value, it should be possible to bind a variable to the result, and to use them inside is().
rule ex21 when {
$ex: is( not Bus( colour == Colour.RED ) )
} then {
System.out.println( "There are " + ($ex ? "" : "no ") + "red busses." );
}
More information about the rules-dev
mailing list