Original File

CLIPS User Guide Chapter 6
CLIPS User's Guide

Chapter 6 Being Functional

Functionality is the inverse of style

In this chapter, you will learn more powerful functions for matching patterns and some that are very useful with multifield variables. You also will learn how numeric calculations are done.

Not My Constraint

Let's reconsider the problem of designing an expert system to help a robot cross a street. One rule that you would have follows.

(defrule green-light

(light green)

=>

(printout t "Walk" crlf))

Another rule would cover the case of a red light.

(defrule red-light

(light red)

=>

(printout t "Don't walk" crlf))

A third rule would cover the case in which a walk-sign said not to walk. This would take precedence over a green light.

(defrule walk-sign

(walk-sign-says dont-walk)

=>

(printout t "Don't walk" crlf))

The previous rules are simplified and don't cover all cases such as the breakdown of the traffic-light. For example, what does the robot do if the light is red or yellow and the walk-sign says walk?

A way of handling this case is to use a field constraint to restrict the values that a pattern may have on the LHS. The field constraint acts like constraints on patterns.

One type of field constraint is called a connective constraint. There are three types of connective constraints. The first is called a ~ constraint. Its symbol is the tilde "~". The ~ constraint acts on the one value that immediately follows it and will not allow that value.

As a simple example of the ~ constraint, suppose you wanted to write a rule that would print out "Don't walk" if the light was not green. One approach would be to write rules for every possible light condition, including all possible malfunctions: yellow, red, blinking yellow, blinking red, blinking green, winking yellow, blinking yellow and winking red, and so forth. However, a much easier approach is to use the ~ constraint as shown in the following rule:

(defrule walk

(light ~green)

=>

(printout t "Don't walk" crlf))

By using the ~ constraint, this one rule does the work of many other rules that required specifying each light condition.

Be Cautious

The second connective constraint is the bar constraint, "|". The "|" connective constraint is used to allow any of a group of values to match.

For example, suppose you wanted a rule that printed out "Be cautious" if the light was yellow or blinking yellow. The following example shows how it's done using the "|" constraint.

CLIPS> (clear)

CLIPS> (defrule cautious

(light yellow|blinking-yellow)

=>

(printout t "Be cautious" crlf))

CLIPS>

(assert (light yellow))

<Fact-0>

CLIPS> (assert (light blinking-yellow))

<Fact-1>

CLIPS> (agenda)

0 cautious: f-1

0 cautious: f-0

For a total of 2 activations.

CLIPS>

And Away We Go

The third type of connective constraint is the & connective constraint. The symbol of the & connective constraint is the ampersand, "&". The & constraint forces connected constraints to match in union, as you'll see in the following examples. The & constraint normally is used only with the other constraints, otherwise it's not of much practical use. As an e xample, suppose you want to have a rule that will be triggered by a yellow or blinking-yellow fact. That's easy enough--just use the | connective constraint as you did in a previous example. But suppose that you also want to identify the light color?

The solution is to bind a variable to the color that is matched using the "&" and then print out the variable. This is where the "&" is useful, as shown below.

(defrule cautious

(light ?color&yellow|blinking-yellow)

=>

(printout t "Be cautious because light is " ?color crlf))

The variable ?color will be bound to whatever color is matched by the field yellow|blinking-yellow.

The "&" also is useful with the "~". For example, suppose you want a rule that triggers when the light is not yellow and not red.

(defrule not-yellow-red

(light ?color&~red&~yellow)

=>

(printout t "Go, since light is " ?color crlf))

It's Elementary

Besides dealing with symbolic facts, CLIPS also can perform numeric calculations. However, you should keep in mind that an expert system language like CLIPS is not primarily designed for number-crunching. Although the math functions of CLIPS are very powerful, they are really meant for modification of numbers that are being reasoned about by the application program. Other languages such as FORTRAN are better for number-crunching in which little or no symbolic reasoning is being done. You'll find the computational capability of CLIPS useful in many applications.

CLIPS provides basic arithmetic and math functions +, /, *, -, div, max, min, abs, float, and integer. For more details, see the CLIPS Reference Manual.

Numeric expressions are represented in CLIPS according to the style of LISP. In both LISP and CLIPS, a numeric expression that customarily would be written as 2 + 3 must be written in prefix form, (+ 2 3). In the prefix form of CLIPS, the function precedes the arguments, and parentheses must surround the numeric expression. The customary way of writing numeric expressions is called infix form because the math functions are fixed in between the arguments.

Functions can be used on the LHS and the RHS. For example, the following shows how the arithmetic operation of addition is used on the RHS of a rule to assert a fact containing the sum of two numbers ?x and ?y. Note that the comments are in infix notation for your information only since infix cannot be evaluated by CLIPS.

CLIPS> (clear)

CLIPS> (defrule addition

(numbers ?x ?y)

=>

(assert (answer-plus (+ ?x ?y)))) ; Add ?x + ?y

CLIPS> (assert (numbers 2 3))

<Fact-0>

CLIPS> (run)

CLIPS> (facts)

f-0 (numbers 2 3)

f-1 (answer-plus 5)

For a total of 2 facts.

CLIPS>

A function can be used on the LHS if an equal sign, =, is used to tell CLIPS to evaluate the following expression rather than use it literally for pattern matching. The following example shows how the hypotenuse is calculated on the LHS and used to pattern match against some stock items. The exponentiation, "**", function is used to square the x and y values. The first argument of exponentiation is the number which is to be raised to the power of the second argument.

CLIPS> (clear)

CLIPS> (deffacts database

(stock A 2.0)

(stock B 5.0)

(stock C 7.0))

CLIPS> (defrule addition

(numbers ?x ?y)

(stock ?ID =(sqrt (+ (** ?x 2) (** ?y 2)))) ; Hypotenuse

=>

(printout t "Stock ID=" ?ID crlf))

CLIPS> (reset)

CLIPS> (assert (numbers 3 4))

<Fact-4>

CLIPS> (run)

Stock ID=B : Stock ID matches hypo tenuse calculated

CLIPS>

Extensive Arguments

Arguments in a numeric expression can be extended beyond two for all functions except The same sequence of arithmetic calculations is performed for more than two arguments. The following example illustrates how three arguments are used. Evaluation proceeds from left to right. Before entering these, however, you may wish to do a (clear) to get rid of any old facts and rules.

(defrule addition

(numbers ?x ?y ?z)

=>

(assert (answer-plus (+ ?x ?y ?z)))) ; ?x + ?y + ?z

Enter the above program and assert (numbers 2 3 4). After you run, you'll see the following facts. Note that the fact-indices may be different if you've done a (reset) instead of a (clear) before loading this program.

CLIPS> (facts)

f-0 (numbers 2 3 4)

f-1 (answer-plus 9)

For a total of 2 facts.

CLIPS>

The infix equivalent of a multiple argument CLIPS expression can be expressed as

arg [function arg]

where the square brackets mean that there can be multiple terms.

Besides the basic math functions, CLIPS has Extended Math functions including trig, hyperbolic, and so on. For a complete list, see the CLIPS Reference Manual. These are called Extended Math functions because they are not considered basic math functions like "+", "-", etc.

Mixed Results

In dealing with expressions, CLIPS tries to keep the mode the same as the arguments. For example,

CLIPS> (+ 2 2) ;both integer arguments give integer

4 ;result

CLIPS> (+ 2.0 2.0) ;both floating-point arguments give

4.0 ;floating-point result

CLIPS> (+ 2 2.0) ; mixed arguments give float result

4.0

Notice that in the last case of mixed arguments, CLIPS converts the result to standard double-precision floating-point type.

You can explicitly convert one type to another by using the float and integer functions, as demonstrated in the following examples.

CLIPS> (float (+ 2 2)) ;convert integer to float

4.0

CLIPS> (integer (+ 2.0 2.0)) ; convert float to integer

4

Parentheses are used to explicitly specify the order of expression evaluation if desired. In the example of ?x + ?y * ?z, the customary infix way to evaluate it is to multiply ?y by ?z and then add the result to ?x. However, in CLIPS, you must write the precedence explicitly if you want this order of evaluation, as follows.

(defrule mixed-calc

(numbers ?x ?y ?z)

=>

(assert (answer (+ ?x (* ?y ?z))))) ; ?y * ?z + ?x

In this rule, the expression in the innermost parentheses is evaluated first; so ?y is multiplied by ?z. The result is added to ?x.

Bound Bachelors

The analog to assigning a value to a variable on the LHS by pattern matching is binding a value to a variable on the RHS using the bind function. It's convenient to bind variables on the RHS if the same values will be repeatedly used.

As a simple example in a math calculation, let's first bind the answer to a variable and then print the bound variable.

CLIPS> (clear)

CLIPS> (defrule addition

(numbers ?x ?y)

=>

(assert (answer (+ ?x ?y)))

(bind ?answer (+ ?x ?y))

(printout t "answer is " ?answer crlf))

CLIPS>

(assert (numbers 2 2))

<Fact-0>

CLIPS> (run)

answer is 4

CLIPS> (facts)

f-0 (numbers 2 2)

f-1 (answer 4)

For a total of 2 facts.

CLIPS>

The (bind) also can be used on the RHS to bind sin gle or multifield values to a variable. The (bind) is used to bind zero, one, or more values to a variable without the "$" operator. Recall that on the LHS, you can only create a multifield pattern by using the "$" operator on a field, such as "$?x". However, the "$" is unnecessary on the RHS because the arguments of (bind) explicitly tell CLIPS exactly how many values to bind. In fact, the "$" is a useless appendage on the RHS.

The following rule illustrates some variable bindings on the RHS. The multifield value function, create$, is used to create a multifield value. Its general syntax is as follows.

(create$ <arg1> <arg2>...<argN>)

where any number of arguments can be appended together to create a multifield value. This multifield value, or a single-field value, can then be bound to a variable as shown in the RHS actions of the following rule.

(CLIPS> (clear)

CLIPS> (defrule bind-values-demo

(initial-fact)

=>

(bind ?duck-bachelors (create$ Dopey Dorky Dinky))

(bind ?happy-bachelor-mv (create$ Dopey))

(bind ?none (create$))

(printout t

"duck-bachelors " ?duck-bachelors crlf

"duck-bachelors-no-() " (implode$ ?duck-bachelors) crlf

"happy-bachelor-mv " ?happy-bachelor-mv crlf

"none " ?none crlf))

CLIPS> (reset)

CLIPS> (run)

duck-bachelors (Dopey Dorky Dinky)

duck-bachelors-no-() Dopey Dorky Dinky

happy-bachelor-mv (Dopey)

none ()

CLIPS>

Doing Your Own Thing

Just like other languages, CLIPS allows you to define your own functions with deffunction. The deffunction is known globally, which saves you the effort of entering the same actions over and over again.

Deffunctions also help in readability. You can call a deffunction just like any other function. A deffunction may also be used as the argument of another function. A (printout) can be used anywhere in a deffunction even if it's not the last action because printing is a side-effect of calling the (printout) function.

The general syntax of a deffunction is shown following.

(deffunction <function-name> [optional comment]

(?arg1 ?arg2 ...?argM [$?argN]) ;argument list. Last one may

(<action1> ;be optional multifield arg.

<action2> ;action1 to

... ;action(K-1) do not

<action(K-1)> ;return a value

<actionK>) ;only last action returned

The ?arg are dummy arguments, which mean that the names of the arguments will not conflict with variable names in a rule if they are the same. The term dummy argument is sometimes called a parameter in other books.

Although each action may have returned values from function calls within the action, these are blocked by the deffunction from being returned to the user. The deffunction will only return the value of the last action, <actionK>. This action may be a function, a variable, or a constant.

The following is an example of how a deffunction is defined to calculate the hypotenuse, and then used in a rule. Even if the variable names in the rule are the same as the dummy arguments, there's no conflict. That's why they're dummy, because they don't mean anything.

CLIPS> (clear)

CLIPS> (deffunction hypotenuse ; name

(?a ?b) ; dummy arguments

(sqrt(+ (* ?a ?a) (* ?b ?b)))) ; action

CLIPS> (defrule calculate-hypotenuse

(dimensions ?base ?height)

=>

(printout t "Hypotenuse=" (hypotenuse ?base ?height) crlf))

CLIPS> (assert (dimensions 3 4))

<Fact-0>

CLIPS> (run)

Hypotenuse=5.0

CLIPS>

Deffunctions may be used with multifield values, as the following example shows.

CLIPS> (clear)

CLIPS> (deffunction count ($?arg)

(length $?arg))

CLIPS> (count 1 2 3 a duck "quacks")

6

CLIPS>

Other Features

Other useful functions follow. For more information, see the CLIPS Reference Manual.

Function Meaning

round Round toward closest integer. If exactly between two integers, rounds toward negative infinity.

integer Truncates the decimal part of a number.

format Formats output

list-deffunctions List all deffunctions

ppdeffunction Pretty print

undeffunction Deletes a deffunction if it is not currently executing and not referred to elsewhere. Specifying "*" for <name> deletes all.

length Number of fields, or the number of characters in a string or symbol

nth$ Specified field if exists, else nil

member$ Number of the field if literal or variable exists, else FALSE

subsetp Returns TRUE if a multifield value is a subsetp of another multifield value, else FALSE

delete$ Given a field number, deletes the value in the field

explode$ Each string element is returned as part of a new multifield value

subseq$ Returns a specified range of fields

replace$ Replaces a specified value