[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