Although you can accomplish a lot with simple facts such as the ones we have already used, in many cases it is desirable to link bits of information together. Consider, for example, an application which is to try to determine the general fitness of a number of people. Several indicators will be used, so each person will have a different value for each one. We could express the information for two people as follows:
(age Andrew 20) (weight Andrew 80) (height Andrew 188) (blood-pressure Andrew 130 80) (age brenda 23) (weight brenda 50) (height brenda 140) (blood-pressure brenda 120 60)
But this involves lots of separate facts, with nothing to link them together other than a single field bearing the person's name. A better way to do this is by using the deftemplate structure, thus:
(deftemplate personal-data (slot name) (slot age) (slot weight) (slot height) (multislot blood-pressure) )
deftemplate does not create any facts, but rather the form which facts can take. Every time a fact of this type is created, it contains the slots specified in its definition, each of which can contain a value and can be accessed by name. Each person's data can now be asserted thus:
(assert (personal-data (name Andrew) (age 20) (weight 80) (height 188) (blood-pressure 130 80)))
or, in a deffacts structure thus:
(deffacts people (personal-data (name Andrew) (age 20) (weight 80) (height 188) (blood-pressure 130 80)) (personal-data (name Cyril) (age 63) (weight 70) (height 1678) (blood-pressure 180 90)))
Although you don't have to specify all the information, so
(assert (personal-data (weight 150) (age 23) (name Brenda)))
is perfectly valid. The order in which you access the slots does not matter, as you are referring to them by name (this also allows you to set as few of them as you wish). Template facts can be altered without the need to retract them and assert a new version. The function modify allows you to change the value of one or more slots on a fact. Suppose it's Andrew's birthday. If we define the rule
(defrule birthday ?birthday <- (birthday ?name) ?data-fact <- (personal-data (name ?name) (age ?age)) => (modify ?data-fact (age (+ ?age 1))) (retract ?birthday) )
then asserting the fact (birthday Andrew) will modify Andrew's age while leaving all his other personal data intact. Incidentally, the reason we retract the birthday fact is for the purposes of truth maintenance. It's only Andrew's birthday once a year, and if we left the fact lying around it would soon become false. Further, every time any other part of Andrew's personal data (weight, for example) was changed the birthday rule would be fired again, causing rapid ageing!
Template-based facts can be used in rules just as ordinary facts, thus:
(defrule lardy-bugger (personal-data (name ?name) (weight ?weight)) (test (> ?weight 100)) => (printout t ?name " weighs " ?weight " kg - the fat sod." crlf) )
But, wait! I hear you cry - what's all this (test (> ?weight 100)) business? That's what is known as a conditional element. It allows a rule to do a certain amount of expression evaluation on the left hand side of the rule. In this case, it will match facts where the value of the weight slot is greater than 100 kg. There are several ways of employing conditional elements in rules. The first is the logical and which is implied simply by using two patterns in the left hand side of a rule, thus:
(defrule print-ages (personal-data (name ?name) (age ?age)) => (printout t ?name " is " ?age " years old." crlf) )
This rule is really saying "if there is a fact with a name and an age, then print it out". The and connective can also be used explicitly if needed, thus:
(defrule print-ages (and (personal-data (name ?name) (age ?age)) (personal-data (name ?name) (weight ?weight)) ) => (printout t ?name " weighs " ?weight " at " ?age " years old." crlf) )
Although the and is superfluous in the above example, it is very useful in more complex logical constructs, as we shall see. We can also use the or connective, thus:
(defrule take-an-umbrella (or (weather raining) (weather snowing) ) => (printout t "Take an umbrella" crlf) )
Which of course means "if it's snowing or raining, take an umbrella". Notice that both or and and are prefix operators, just like addition or subtraction, so you write (or (thing1) (thing2)), not ((thing) or (thing2)). The other conditional element recognisable from traditional logic is not, which simply negates the truth of a predicate, thus:
(defrule not-birthday (personal-data (name ?name) (weight ?weight)) (not (birthday ?name)) => (printout t "It's not " ?name "'s birthday" crlf) )
Now, back to that test element. Using test allows you to check anything in the left hand side of a rule, not just facts. So, we could write a rule which (for no readily apparent reason) checks to see if six is greater than five:
(defrule pointless (test (> 6 5)) => (printout t "Six is indeed greater than five" crlf) )
The final two conditional elements are exists and forall. exists is satisfied if there are one or more facts which match its predicate; the rule containing it is fired only once regardless of how many facts it matches. forall, on the other hand, triggers once if every fact matches its pattern. Make sure you have the personal data of a few people on the fact base, then try the following two rules:
(defrule person-exists (personal-data (name ?name)) => (printout t "Rule person exists reports there is a person called " ?name crlf) ) (defrule are-there-people (exists (personal-data (name ?name))) => (printout t "Rule are-there-people reports there is at least one person" crlf) ) (defrule check-each-person (forall (personal-data (name ?name)) ( ) => (printout t "Rule check-each-person reports that all persons have a name" crlf) )
Now try creating a new person without a name, and run them again. Rule check-each-person will not fire because it is not true for all facts which match its pattern.
Elements introduced in this tutorial: template facts, conditional elements
(deftemplate (slot slotname1) (slot slotname1)) (and (predicate1) (predicate2)) (or (predicate1) (predicate2)) (not (predicate1)) (test (predicate1)) (exists (pattern)) (forall (pattern1) (pattern2)