We originally implemented this demonstration in CLIPS (see this post) but decided Clojure would provide a better platform due to advantages discussed below.
“An expert system is a program capable of pairing up a set of facts with a set of rules to those facts, and execute some actions based on the matching rules. ”
At 180 centimeters, Emily Williams stands tall. She carries a “rectangular” body shape. She wants to select a purse.
We consider two fashion rules related to the selection of purses for individuals with rectangular body shapes , and reason upon them using an expert system:
- If you have a rectangular body shape and are tall, select a large purse.
- If you have a rectangular body shape and are petite, select a small purse.
Moreover, we will demonstrate “cascading effects”, where the conclusion of one rule in an expert system automatically prompts the firing of additional rules based on the conclusions of the first rule.
Notice first that variable values such as “rectangular”, “tall”, “petite”, “large”, and “small” are rather imprecise, whereas specifying Emily’s exact height in centimeters is precise. In a later post we’ll demonstrate the addition of fuzzy logic and fuzzy reasoning to our expert system to account for, say for example, differences in expert opinion about what “tall” means.
We’ll implement our example expert system in Clojure using Clara .
Setting Up the Data Model
We define two data structures, one to hold information about the person whom a purse recommendation is to be made for, and one to contain the purse recommendation itself. The latter is really unnecessary; just something we are supplying to demonstrate a cascade effect.
The first data structure:
(defrecord Person [name height favorite-color body-shape])
Basically we create a record of a person’s features such as their name and height. We’ll use the height and body-shape features to match to the fashion rules detailed above, use the name feature for output, and the favorite-color feature to demonstrate a cascade effect.
The second data structure:
(defrecord PurseRecommendation [purse-size name])
In this we store a recommended purse size and, for clarity, a reference to whom the recommendation applies to.
The Fact Repository
The rules of an expert system operate on a universe of facts presented to it, which we call the “fact repository”. We present the following facts to our system in the “main” function using the “insert” command:
(defn -main "Run this example."  ;; Create a session with our person information. (let [session (-> (mk-session 'clara.purse) (insert (->Person :Emily :tall :pink :rectangle) (->Person :Sally :petite :blue :rectangle) ))]
We define two almost identical rules to match body shape and height to a purse recommendation, one for tall individuals and one for petite individuals. Note that we could combine these into one rule with IF-THEN-ELSE logic (we are not yet sure which design pattern proves most effective for expert system construction):
(defrule make-a-purse-recommendation-for-rectangular-body-shape-tall-body-height "Rule for tall rectangular body shapes." [?person <- Person (= height :tall) (= ?name name)] => (output-a-recommendation ?person "large" ?name) )
(defrule make-a-purse-recommendation-for-rectangular-body-shape-petite-body-height "Rule for petite rectangular body shapes." [?person <- Person (= height :petite) (= ?name name)] => (output-a-recommendation ?person "small" ?name) )
The first rule fires for each person existing in the fact repository having a rectangular body shape and tall stance. The second rule works similarly for those with rectangular body shapes and petite stance. Each then fires the “output-a-recommendation” function to display calculation results:
(defn output-a-recommendation "Outputs the initial purse recommendation." [person size name] (println name "should use a" size "purse.") (insert! (->PurseRecommendation size name)) )
<h1″>Defining a Rule that Demonstrates a Cascade Effect
The “insert” command in above function adds a new fact to the fact repository. Immediately upon generation, if the fact is a purse recommendation, the following rule responds:
(defrule show-a-cascade-effect "Rule triggered when a purse recommendation is made." [?purse-recommendation <- PurseRecommendation (= ?size purse-size) (= ?name name)] [?person <- Person (= ?favorite-color favorite-color) (= name ?name)] => (output-a-follow-up-recommendation ?name ?favorite-color ?size) )
This matches any purse recommendation, saving the recommended size as ?size and the individual’s name for whom the recommendation was made as ?name. It then matches individuals of name ?name to determine their favorite color ?favorite-color. Finally, it outputs customized text by calling the following function:
(defn output-a-follow-up-recommendation "This demonstrates how when a fact is inserted by a function, it automatically triggers rules related to the fact." [name color size] (println "CASCADE EFFECT: " name "should sport a" size color "purse!") )
The Expert System In Action
We run the system and observe the following output:
:Emily should use a large purse. :Sally should use a small purse. CASCADE EFFECT: :Emily should sport a large :pink purse! CASCADE EFFECT: :Sally should sport a small :blue purse!
Everything works as expected!
This example demonstrates a simple expert system in action. Importantly, we observed how the fulfillment of one rule automatically enables the triggering of another rule based on the first rule’s conclusions—we do not have to explicitly program this behavior.
The advantage of using Clojure over CLIPS for this task is that Clojure uses the Java Virtual Machine, meaning that Java libraries may be utilized as well for tasks such as web development and MapReduce. Moreover, Clojure is a modern language, meaning experienced developers are available today, whereas CLIPS proves extremely dated.
As alluded to above, we did not discuss fuzzy reasoning in this post, despite the fact that the variable values we encountered in this demonstration prove inherently imprecise. We’ll address that in a forthcoming article.