Control
Simply encoding the rules is not enough, we must also have control over them. Since it is impossible to fire all of the currently valid rules simultaneously, there must be a conflict-resolution strategy. This is more important in very large programs where rule sequencing can have a great effect on the speed of execution, but even the smallest program must have a means of choosing the next rule to fire.
The simplest way to use rules like these in a program is to start with the data an allow the rules to work toward a solution through a process called forward chaining. This can be done by checking each rule in turn to see if the available data (the environment or context) satisfies the IF portion and, if it does, performing the action in the THEN part. When you reach the end of the rules, you have made all the deductions that can be made on the basis of the initial data.
This is the simplest kind of a rule-based system. The rule module is called and it looks at some global variables and draws certain conclusions. These conclusions result in changes in the global database. Then the module returns.
Listing One is an example of forward chaining. It is a toy system to help decide what means of transportation to use for a trip. The main program begins by gathering all the necessary data and filling some of the global variables. It then calls the rule module that processed the data and fills in some more of the global variables with its results. (In the interest of simplicity, all these variables are Booleans.) Finally, the main program reports its conclusion to the user.
You have probably been taught to avoid global variables (and for good reason) but using them simplifies a small system like this one. For larger programs, it is better to pass structures to the rule modules and avoid global variables.
Notice that some of the rules produce data used by other rules. The order in which the rules are written insures that the data is ready when needed. The rules are checked one at a time in the order they are written and each of them fires if it's IF part is satisfied. This is control, even though it is so simple.
Listing Two is modified so the rules may appear in any sequence. For the rules function be independent of rule order, it must go through the rules more than once. If you continue to loop through the rules until none of them fires, you are assured of drawing all the conclusions the data supports.
#include "stdio.h" #define FALSE 0 #define TRUE 1 #define UNKNOWN 2 int av_speed; char like_scenery = UNKNOWN, is_pilot = UNKNOWN, fly = UNKNOWN, drive = UNKNOWN, fly_com = UNKNOWN, fly_tcart = UNKNOWN, fly_bon = UNKNOWN, m_cycle = UNKNOWN, car = UNKNOWN; main() { int done, distance, /* in miles */ time; /* in hours */ char c; char str[10]; printf("This is a program to help you with travel planning.\"); printf("\n\nHow far are you going? (miles)\n"); gets(str); distance = atoi(str); printf("\n\nHow much time do you have for the trip? (hours)\n"); gets(str); time = atoi(str); av_speed = distance/time; printf("n\%d\n", av_speed); printf("Do you prefer scenery over speed? (Y/N)\n"); gets(str); if(str[0] == 'Y') like_scenery = TRUE; else like_scenery = FALSE; printf("Are you a pilot? (Y/N)\n"); gets(str); if(str[0] == 'Y') is_pilot = TRUE; else is_pilot = FALSE; rules(); if(fly_com == TRUE printf("\nFly commercial."); if(fly_tcart == TRUE printf("\nRent a Taylorcraft and fly low."); if(fly_bon == TRUE) printf("\nRent a Bonanza and fly high."); if(m_cycle == TRUE) printf("\nTake your motorcycle and ride the back roads."); if(car == TRUE) printf("\nThere's nothing for it but to drive a car."); } rules() { char done = FALSE; while(done == FALSE){ done = TRUE; if(av_speed > 60 && fly == UNKNOWN){ fly = TRUE; done = FALSE; } if(av_speed <= 60 && drive == UNKNOWN) { drive = TRUE; done = FALSE; } if(fly == TRUE && is_pilot == FALSE && fly_con = UNKNOWN){ fly_con = TRUE; done = FALSE; } if(fly == TRUE && is_pilot == TRUE && like_scenery = TRUE && av_speed < 100 && fly_tcart = UNKNOWN) { fly_tcart = TRUE; done = FALSE; } if(fly == TRUE && is_pilot == TRUE && (100 < av_speed) && (av_speed < 200) && fly_bon = UNKNOWN) { fly_bon = TRUE; done = FALSE; } if(drive == TRUE && m_cycle == FALSE && car = UNKNOWN) { car = TRUE; done = FALSE; } if(drive == TRUE && like_scenery == TRUE && m_cycle = UNKNOWN) { m_cycle = TRUE; done = FALSE; } if(drive == TRUE && like_scenery == FALSE && m_cycle = UNKNOWN) { m_cycle = FALSE; done = FALSE; } } /* end while */ } /* end rules */
This can be done with a flag to indicate the firing of a rule. In Listing Two this flag is called done. Each rule makes done false if it fires. This insures that the module will repeat until it exhaust all changes. Within each iteration the rules are tested in order, just as in Listing One.
It is necessary to prevent rules that have fired in a previous iteration from firing again, which would have no effect on the data but would cause the function to loop endlessly. This can be accomplished several ways, one of which is shown in Listing Two: the variables are three-valued and initialized to 2, which represents UNKNOWN.
The rules have a new check in the IF part that keeps the rule from firing unless the variable to be changed is UNKNOWN. For example, the first rule shown in Listing Two is:
if (av_speed > 60 && fly == UNKNOWN) } fly = TRUE: done = false
When the function rules is called and this rule is tested for the first time, the variable fly y will have its initial value of UNKNOWN or 2. If the global variable av_speed is greater than 60, this rule will fire and change the value of fly to TRUE. This rule cannot fire again in a future iteration of rules because fly is now TRUE and the rule cannot fire unless fly is UNKNOWN.
But this strategy introduces another order dependency. If two rules of this form affect the same variable, the final value of the variable depends on the order of the rules. The one that fires first prevents the other from firing. This is not necessarily bad, but you must be aware of it.
It is possible to accomplish nearly the same result by preventing rules from firing unless they will change the value of some variable. The previous example could be rewritten:
if (av_speed > 60 && fly ! = TRUE{ fly = TRUE done = FALSE }
This prevents the rule from firing when it would have no effect. However, it is now possible for two rules to change a variable back and forth to another.
These strategies may be mixed. You might write a rule that will fire and give a value to a variable only if that variable is UNKNOWN. Then a later rule, if otherwise satisfied, might fire and give a new value to the same variable if it does not already have that value:
if (av_speed > 60 && fly UNKNOWN) { fly = TRUE; done = FALSE; } if (hate_flying) && fly ! = FALSE){ fly = FALSE; done = FALSE; }
This has the effect of allowing the hate_flying rule to override the av_speed rule regardless of the order on which they occur. Also, two or more rules can give different values to the same variable without creating an endless loop.