Monday, November 09, 2009

What is inference and how does it facilitate good rule design and maintenance

Inference has a bad names these days, as something not relevant to business use cases and just too complicated to be useful. It is true that contrived and complicated examples occur with inference, but that should not detract from the fact that simple and useful ones exist too. But more than this, correct use of inference can crate more agile and less error prone businesses with easier to maintain software.

So what is inference? Something is inferred when we gain knowledge of something from using previous knowledge. For example given a Person fact with an age field and a rule that provides age policy control, we can infer whether a Person is an adult or a child and act on this.
rule "Infer Adult"
when
$p : Person( age >= 18 )
then
insert( new IsAdult( $p ) )
end
So in the above every Person who is 18 or over will have an instance of IsAdult inserted for them. This fact is special in that it is known as a relation. We can use this inferred relation in any rule:
    $p : Person()
IsAdult( person == $p )
In the future we hope to improve our language so you can have special handling of known relation facts, so you can just do following and the join is implicit:

Person() IsAdult( )
So now we know what inference is, and have a basic example, how does this facilitate good rule design and maintenance?

Let's take a government department that are responsible for issuing ID cards when children become adults, hence forth referred to as ID department. They might have a decision table that includes logic like this, which says when an adult living in london is 18 or over, issue the card:


However the ID department does not set the policy on who an adult is. That's done at a central government level. If the central government where to change that age to 21 there is a change management process. Someone has to liaise with the ID department and make sure their systems are updated, in time for the law going live.

This change management process and communication between departments is not ideal for an agile environment and change become costly and error prone. Also the card department is managing more information than it needs to be aware of with its "monolothic" approach to rules management which is "leaking" information better placed else where. By this I mean that it doesn't care what explicit "age >= 18" information determines whether someone is an adult, only that they are an adult.

Instead what if we were to split (de-couple) the authoring responsibility, so the central government maintains its rules and the ID department maintains its.

So its the central governments job to determine who is an adult and if they change the law they just update their central repository with the new rules, which others use:


The IsAdult fact, as discussed previously, is inferred from the policy rules. It encapsulates the seemingly arbitrary piece of logic "age >= 18" and provides semantic abstractions for it's meaning. Now if anyone uses the above rules, they no longer need to be aware of explicit information that determines whether someone is an adult or not. They can just use the inferred fact:


While the example is very minimal and trivial it illustrates some important points. We started with a monolithic and leaky approach to our knowledge engineering. We create a single decision table that had all possible information in it that leaks information from central government that the ID department did not care about and did not want to manage.

We first de-coupled the knowledge process so each department was responsible for only what it needed to know. We then encapsulated this leaky knowledge using an inferred fact IsAdult. The use of the term IsAdult also gave a semantic abstraction to the previously arbitrary logic "age >= 18".

So a general rule or thumb when doing your knowledge engineering is:

Bad
  • Monolithic
  • Leaky
Good
  • De-couple knowledge responsibilities
  • Encapsulate knowledge
  • Provide semantic abstractions for those encapsulations

6 comments:

  1. Thanks Mark, this was very informative. I can see some refactoring I can do as well in my own rule design.

    ReplyDelete
  2. Hi Mark,

    From what I understand moving the logic of IsAdult to a fact means writing it in an imperative form, I can then reason that every rule in my rule base could be rewritten that way, just by taken the necessary arguments in a fact's constructor.

    Let's suppose I have a rule to decide whether or not a student is eligible for student aid. I would then have a fact to decide whether or not a person is a student, something like IsStudent(), then the FSA could code it's rules for eligibility inside their IsEligible fact, which I would retrieve from their "rule/fact" base.

    In this scenario, aren't we moving logic that would be better expressed in a declarative form (drl) to facts that are coded in java?

    I might have missed something here.

    ReplyDelete
  3. From the perspective of a member of a development team new to rules, I think this is a good illustrative example of the basic concept of inference.

    As we dig a little deeper, it does expose questions about the best way to assert inferences. Some people modify fields in the fact, but we are dealing with records and don't want to leave our fingerprints all over them. I got excited about metadata, until I realized that it is associated with the class rather than instance/object. In the example, you insert a new object for each assertion, which makes me vaguely uncomfortable about scalability and persistence. Now I'm thinking I need a shadow object for each main fact (record) where we capture many assertions/inferences about that record (mostly inferences about validity of the record). I think your example is a great introduction, and I would love to see a follow-on that discusses alternative representations of inferred facts.

    ReplyDelete
  4. "In this scenario, aren't we moving logic that would be better expressed in a declarative form (drl) to facts that are coded in java?"

    That facts are coded in java is incidental there use is still very declarative. Imperative is "how" and often procedural in nature. By using the inferred fact the de-coupling makes sure it's not procedural, the age policy rules do not invoke the card issuing rules. Secondly the encapsulation and the abstraction makes sure the card issuing rules do not know the "how" someone is an adult, they have just declaratively been told that they are.

    ReplyDelete
  5. Hi Marc, what is your rule of thumb wether to use a "declare" or a Java fact? For IsAdult we would use a declare. This solution seems to us nice and easy. Nevertheless we currently missing the possibility to define constructors inside a declare, so that we need s.th. like:

    declare IsAdult
    person : Person
    end

    ..
    then
    IsAdult isAdult = new IsAdult();
    isAdult.setPerson($p);
    ..

    ReplyDelete
  6. Yeah, I have missed the point :)

    Thanks for clarifying, Mark.

    It seems an interesting approach, I would just need to make sure that the rules that create the inferred facts run before my rules (those that use the inferred facts), b/c I imagine that if I have a halt or no-loop in one of my rules, they might end-up evaluating to false b/c the some inferred facts might not be there yet.

    ReplyDelete