home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Current Shareware 1994 January
/
SHAR194.ISO
/
cad_util
/
alsps.zip
/
ALSP9.DOC
< prev
next >
Wrap
Lisp/Scheme
|
1993-11-04
|
72KB
|
1,754 lines
AutoLISP Tutorial #9
Conditional Branching, Part Two:
More AutoLISP branching functions.
Symbolic Logic.
Predicates.
Logical Operators.
Contributed by Tony Tanzillo,
A/E Automation Systems.
Recap:
In part one, installment #8 in this series, we were introduced to
conditional branching. We used the AutoLISP IF conditional branching
function in several excercises that helped illustrate how we can make
program flow branch into one of several possible paths of execution,
causing them to respond differently to various results of a test or
state.
In this installment, we'll look more closely at how a program can
perform various tests using PREDICATES, and how we can make use of
the outcome of these tests to control program logic flow by using
simple and complex tests as the branching condition.
We'll also learn how to control program flow with complex conditions
by using LOGICAL OPERATORS to operate on logic in a manner similar to
the way we operate on numbers, and to combine multiple logical states
into a single complex condition.
Finally, we will allso be introduced to several AutoLISP primitives
that do conditional branching: COND and WHILE, and we will learn how
to use the PROGN function to place several expressions where only one
is expected.
For the benefit of those who are new to LISP in general, but who may
have some experience in other languages, we will also attempt to
simplify an important distinction of LISP, with respect to the way
logic is represented and interpreted by LISP conditional branching
functions.
1. Symbolic Logic.
Every computer programming language provides a means of representing
the two boolean logic states of "TRUE" and "FALSE", and this is the
essence of what all conditional branching and automated reasoning is
based on. In most programming languages, numeric or integer values
are used to represent logic states, where 0 represents FALSE, and any
other numeric value represents TRUE. What determines this, depends
on how all conditional branching constructs of a language choose to
interpret a numeric value. Most conventional languages use numeric
values explictly, to represent a state of logic. LISP does not use
numeric values to convey logic.
LISP uses what can be referred to as SYMBOLIC LOGIC. In the context
of using a value as the condition for branching, we can classify all
possible values as being either NIL or NON-NIL, where the only value
that can represent the FALSE state is the special symbol NIL, which
itself implys the absence of a value. This concept is a major source
of confusion for those migrating to LISP with some experience in
other less symbolic languages.
Aside from NIL, all other LISP values are considered to be "NON-NIL",
values, regardless of what the value is, or type of data, a NON-NIL
value will always be interpreted as meaning "TRUE", when it appears
in a branching function as the condition.
Consider these two very similar code fragments in LISP and `C':
LISP: C:
(setq x 0) { int x = 0;
... ...
(if x (princ "X is TRUE\n") if(x) printf("X is TRUE\n");
(princ "X is FALSE\n") else printf("X is FALSE\n");
) }
In both cases, the value of X is the INTEGER 0, and this value itself
is the condition that determines which of the two result expressions
within the IF constructs will be selected for execution. In the 'C'
example, giving X a value of 0 results in a FALSE condition. On the
other hand, in the case of the LISP example, giving X the same value
would result in a TRUE condition, causing it to proclaim "X is TRUE".
This is because LISP, unlike many other languages, does not interpret
the numeric value 0 as representing the logic state FALSE.
Some simple examples follow. All of the expressions will print out
"TRUE", because in each case the first argument to IF is considered
to represent a TRUE state:
(if "MOO" (print "TRUE") (print "FALSE")) ; IN LISP, ALL
;
(if 0 (print "TRUE") (print "FALSE")) ; VALUES EXCEPT NIL
;
(if 'SYMBOL (print "TRUE") (print "FALSE")) ; HAVE THE LOGICAL
;
(if T (print "TRUE") (print "FALSE")) ; VALUE OF "TRUE".
In fact, the ONLY test value that could trigger the "ELSE" expression
in one of the above examples, is the symbol Nil.
Binary or "bitwise" logic.
There are other ways of representing logic in LISP. For example, the
AutoCAD SYSTEM VARIABLES that represent an ON/OFF state or setting do
not return NIL and NON-NIL values. Instead, the integer values 0 and
1 are used. In addition, certain AutoCAD entities can posses integer
data fields that represent logical "flags". This is commonly known as
as binary or "bitwise" logic, and AutoLISP provides special functions
for performing logical operations on these values as well.
We'll be exploring bitwise logic and the various operations that we
can apply to bit-coded numbers in much greater detail in a future
installment, where we will use bitwise logical operations to examine
entity data, and to create boolean truth tables. As as an exercise,
we will make use of LISP bitwise logical operators in an attempt at
creating a rectangle clipping function using the Cohen-Sutherland
line clipping algorithm in AutoLISP.
2. Predicates.
In order for a program to branch to different paths of execution, it
must use some criteria for determining which branch should be taken.
So it follows that in order to determine this criteria, some kind of
test must be performed. The kinds of tests that can be performed and
used as the basis for doing conditional branching are limitless in
scope and complexity. The test can be as simple as interpreting the
logical value of any variable (perhaps one that was entered by the
user), examining the value of an AutoCAD system variable; or it can
be complex as searching for a string in a file, or an entity with a
particular property value in a drawing.
There are functions in AutoLISP which are specifically designed to
perform tests, and which always produce a value that indicates the
outcome of the test. In another sense, we can really think of these
functions as being able to provide answers to questions which the
program may require at runtime, so:
Predicates are functions that answer questions.
Usually, when a program branches to one of several possible paths of
execution, the decision of which branch it is to take is based on the
answer to one or more specific questions, which can be obtained by
using PREDICATE functions. Some AutoLISP predicate functions can be
easily identified by their symbols, which end with the character 'P'
(e.g., NumberP, ListP and MinusP). But note that not all predicates
share this characteristic. For example, the ATOM function is also a
a predicate, because it returns T if its argument is an ATOM.
All predicate functions return a value to their caller that indicates
whether the test which they performed was a success or failure. All
predicates return the symbol NIL if their tests failed, representing
a FALSE state.
However, if a test was a success, many predicates will return the
symbol T (which is just one instance of a NON-NIL value) to represent
a TRUE state. But, returning the symbol T to indicate success is not
always the case. A predicate function may return any value except
NIL, to indicate a success, and regardless of what the value is, it
always represents TRUE.
For example, the AutoLISP MEMBER predicate indicates if a specified
item exists in a LIST. If the element is found in the list, MEMBER
doesn't return the symbol T. Instead, it returns the portion of the
list that starts with the specified element being searched for. This
makes MEMBER much more useful than it would be if it simply returned
the symbol T. The reason why it can return a LIST and still be used
as a predicate, is because when used within a conditional function
like IF, any value would have the very same effect as the symbol T.
Here are some typical questions which a program may need the answers
to, along with their LISP equivalents as calls to AutoLISP predicate
functions which provide the answers to those questions:
Questions: Predicate forms:
-------------------------------------------------------------------
Are these two numbers equal? (= num1 num2)
Is this a string? (eq (type "hello") 'string)
Does this string appear in this list? (member "Z" '("X" "Y" "Z"))
Is this a number? (numberp 12.0)
Is num1 greater than num2? (> num1 num2)
Is this a list? (listp "FOO")
Is this number negative? (minsup -3.0)
Is this number zero? (zerop x)
Are these sorted in descending order? (apply '> (list v1 v2 v3))
An example of a predicate function used as a branching condition:
; get an integer from the user, and assign it to the symbol 'VAL':
(setq val (getint "\nEnter a positive number: "))
; see if the user-supplied value in VAL is negative, and if
; so, then print a message indicating such:
(if (minusp val) ;; if val is negative,
(princ "\nValue cannot be negative!") ;; then do this,
(princ "\nValue is OK")... ;; else do this
)
Here, the predicate MINUSP is being used to provide an answer to the
question: "Is this number negative?"
Writing your own predicates.
You've already written one predicate function, that is, if you had
followed the exercise in the last installment of this series, you
created the function GREATER, which is really a predicate function
that answers a question (is the argument greater than 0?). But in
that case, the function provides the answer to you, in a form that
you understand, but not in a form that can be interpreted and used by
another program or function. In AutoLISP, all predicate functions
supply the result or outcome of the test performed in a form that can
be interpreted (logically) by its caller.
It is easy to write your own predicate functions that can perform
just about any kind of test you could possibly need. What follows
is a very simple example of a user-defined predicate function called
EVENP, a predicate that answers the question: "Is this number EVEN?"
First, we know that any number that is EVEN is equally-divisible by
two, so we can use the AutoLISP REMainder function to determine if
there is a non-zero remainder left after the division.
We won't need our text editor for this so just fire up AutoCAD, and
we'll define the function right on the command line:
Command: (defun evenp (num) (zerop (rem num 2)))
EVENP
Command:
Notice that within the body of this user-defined predicate, we are
calling another AutoLISP predicate called ZEROP, which obviously
answers the question: Is this value equal to ZERO? This is often
the case when we abstract new predicates, that their results can be
based on the result of performing one or more tests involving one
or more predicates.
In fact, the predicate ZEROP could less-effeciently be written as:
(= num 0)
Which obviously would produce the same result as ZEROP.
Now, let's try out our new predicate function:
Command: (evenp 3)
nil
Command: (evenp 12)
T
Command:
If the argument to EVENP is an even number, the function will return
the symbol T, which is a non-nil value that serves to indicate a TRUE
state. This value can then be used by a calling expression as the
condition that determines which branch of code is to be evaluated
next. Conversely, if the argument is ODD, then EVENP returns the
symbol NIL, indicating a false condition.
Great! Now suppose we also wanted to write the INVERSE predicate
function to EVENP, a predicate called ODDP which is to return T if
its argument is an ODD number, and NIL otherwise?
In the next section we'll be introduced to LOGICAL OPERATORS, which
are functions that can operate on logical values in various ways.
And, we will see how it is possible to apply a logical operator to
the result of EVENP, and derive from it, the inverse predicate: ODDP.
We've already seen one example of a user-defined predicate, as well
as an example of how we could use the various predicates AutoLISP
provides, and we will be defining and using more predicate functions
in other examples throughout this tutorial.
3. Logical Operators.
So far, we have seen how it is possible to use IF to branch on a
single condition. In the above example, the condition was simply a
test to determine if the number supplied by the user was negative.
And we have also seen that PREDICATE functions can be used as the
basis or condition for branching, permitting the branching function
to determine if the test condition is true, and then select a path
of execution based on the answer provided by the predicate.
In LISP, a logical operator is a function that accepts either one or
several "connective" logical states (represented by NIL and NON-NIL
values in LISP but which are generally referred to as either TRUE or
FALSE). These functions perform a "logical operation" on true/false
logic states to derive ONE new logical state as a result.
Logical operations.
There are three basic logical operations that can be performed on one
or more logic states. There are several other logical operators that
can only be abstracted using a more complex combination of any two or
three basic operators, but we'll stick to the basic ones here.
In terms of logical operations and the values they operate on, there
are only two possible values (or "logic states") that we can apply
any logical operation to: TRUE and FALSE. All logical operations must
take at least one logical value as their input (arguments), and all
logical operations produce only ONE value as their output (result).
The three basic connective logical operations:
Operator Input Output
--------------------------------------------------------------------
AND Any number of TRUE/FALSE TRUE when ALL input states
logic states. are TRUE, otherwise FALSE.
--------------------------------------------------------------------
OR Any number of TRUE/FALSE TRUE when ANY one input state
logic states. is TRUE, otherwise FALSE.
--------------------------------------------------------------------
NOT 1 TRUE/FALSE logic state. TRUE if the input state is
FALSE, or FALSE if the input
state is true (complement).
Later in this section, we will also define another logical operator,
a variation of OR called the XOR operator (exclusive OR), which can
be useful to help make composite logical operations less complex.
What do logical operators do?
If you recall from lesson 8, we saw a very simple example of how we
ourselves routinely do conditional branching in our minds, to make
everyday decisions:
"If it is sunny today, I will not take
my umbrella with me to work."
A simple statement with a precedent and consequent, right?
Now, consider the following connective:
"If it is raining, AND the temprature is below 32 degrees,
then I will drive very carefully."
How many conditions are there in the precedent?
If you see more than one state or condition, then considering the key
word that makes this statement a connective, which also appears in
the table above, how many of these unique conditions must be TRUE, in
order for the consequent to prevail?
In the above connective, there are TWO discrete logical states that
enter into a single "logical AND" operation to produce a result which
becomes the condition which ultimately determines if the consequent
part of the statement holds.
The AutoLISP AND function.
Syntax: (and <expr>...)
The AND function is a logical operator that takes one or more logical
values as arguments and operates on them to produce a single logical
result. The operation that is performed, is quite simply, that if
all the logical input states are NON-NIL (true), then the result of
the operation is NON-NIL (true). If any one of the arguments is NIL
(false), the result is also NIL.
So, we can use AND to permit a conditional function to determine what
branch of code is to be evaluated when ALL of several logical states
are TRUE.
Here are some simple examples of using AND:
Expression Result
--------------------------------
(and T T T NIL t) nil
(and T nil t nil nil) nil
(and T T nil t t) nil
(and T T T T T) T <--- All arguments are NON-NIL
(and nil 3 t) nil
(and T 4 "FOO" T) T <--- All arguments are NON-NIL
One important but not so obvious characteristic of AND, is that it
is a 'special form', that does what is referred to as CONDITIONAL
EVALUATION. Meaning that AND may not have to evaluate all of its
arguments to determine its result. The result of any AND function is
known when the first NIL value is found, and the need to continue
evaluating additional arguments after this is entirely pointless, so
AND will never evaluate any argument that proceeds an argument which
evaluates to NIL.
This is illustrated in the above examples. Note that the symbol `T'
appears in both upper and lower case. Upper-case T's represent the
results of all arguments that must be evaluated to determine what the
result of the AND expression is. On the other hand, a lower-case 't'
represents the `would-be' result of an expression that would not be
evaluated, since the outcome of the entire AND expression is already
known before those arguments are encountered.
One very common use of AND in AutoLISP stems from the fact that it
does conditional evaluation. This lets us arrange to have a program
terminate naturally, right at the point where something may have gone
astray (perhaps at the point where the user fails to supply required
data that makes it impossible to continue, for example).
You can use AND to cause your program to terminate immediately, as
soon as the user fails to do something that is expected of them, at
any interval, or immediately after any response to any input prompt
and you can do it without having to write "spaghetti code". So, now
let's take a look at how our programs can exploit special forms, and
their use of conditional evaluation.
A functional example:
The AutoLISP (vertex) function which follows, can be invoked at any
AutoCAD prompt where a coordinate is expected. It will permit you to
compute and use as input, the point where any two imaginary lines of
infinite length, which pass thru two pairs of coordinates, intersect.
This program demonstrates the use of AND as a means of preventing a
program from continuing to obtain input from the user, if one of the
values that was expected was either not supplied, or was an improper
value.
Note that both the INPUT and ASSIGNMENT of the four coordinates is
done entirely within the AND construct as subexpressions. The reason
for arranging it this way is simple: The AND function will detect
the first invalid (NIL) response to any of the four prompts AS SOON
AS IT IS ENTERED, and will immediately suspend evaluation of any and
all proceeding arguments. And, since one of the side-effects of the
expressions within the AND construct is the act of getting all input
from the user, no additional input will be requested, and the program
will just display a message indicating that a value that was expected
was not properly supplied, and terminate.
This is one way to validate user input, and is especially useful when
a given value supplied by the user must meet all of several specific
criteria (e.g., a user-supplied number must fall within the range of
two defined values, or perhaps where a specific entity type is to be
selected). For those who may not be familiar with it yet, we will in
a future installment be introduced to other ways AutoLISP provides us
to ensure that all user input is supplied and valid.
The VERTEX function.
(defun VERTEX (/ p1 p2 p3 p4)
(if (and (setq p1 (getpoint "\n>>First line from: ")) ; IF p1 AND p2
(setq p2 (getpoint p1 " to: ")) ; AND p3 AND p4
(setq p3 (getpoint "\n>>Second line from: ")) ; are NON-NIL,
(setq p4 (getpoint p3 " to: ")) ;
) ;
(inters p1 p2 p3 p4 nil) ; THEN do this,
(prompt "\n>>Must specify four points.") ; ELSE do this.
)
)
Another example:
Our second example that uses AND, will be used in another excercise
later in this installment, as well as with a slightly more complex
project which we'll undertake in a future installment.
This example is a user-defined PREDICATE function that indicates if a
subject point lies on or within a rectangular extents defined by two
points.
First, we'll look at the problem graphically:
p1+-----------------+p2
| |
| +PT | <-- Our predicate is to return NON-NIL if the
| | point 'PT' lies on or within the rectangle
| | defined by two endpoints of either diagonal
| |
p1+-----------------+p2
One way to find out if the point is on or within the rectangle, is to
determine if the point is within range of both the X and Y ordinates
of two of the four corner points forming either diagonal of the
rectangle. Therefore, what we have is two connective logical states
(conditions) that must be satisfied for the compound predicate to
prove to be TRUE. Each of these conditions can easily be determined
by using the >= predicate, which indicates if its arguments appear in
descending order or if any two or more adjacent arguments are equal.
For example:
(>= 12 10 8 8 5) returns -> T (adjacent equal values are OK)
(>= 7 5 6) returns -> nil (not in descending order)
So, we can now develop the two following test prototypes:
(>= Xmax pX Xmin) ; This indicates if the X ordinate of
; the point is within the X range of the
; rectangular extents.
(>= Ymax pY Ymin) ; This does the same with the Y ordinates.
In the above expressions, Xmax and Ymax are the ordinates of the
upper-rightmost corner of the rectangle, and Xmin and Ymin are the
ordinates of the lower-left corner. So, if the subject point pX,pY
is within the rectangle, both its X and Y ordinates must be within
the X and Y ordinates of these two corner points.
Remember that a 2D point is a list of 2 reals, and so (car <point> )
returns the X ordinate of a point, and (cadr <point> ) returns the Y
ordinate of a point. Now, we can use the AutoLISP (MAX) and (MIN)
functions to find both the low and hi X and Y ordinates of the two
points on either diagonal like this:
; c1 and c2 are the two points on either diagonal of the
; rectangular extents:
(setq x-max (max (car c1) (car c2))) ; greater X ordinate
(setq x-min (min (car c1) (car c2))) ; lesser X ordinate
And, we can do the same for the Y ordinate:
(setq y-max (max (cadr c1) (cadr c2))) ; greater Y ordinate
(setq y-min (min (cadr c1) (cadr c2))) ; lesser Y ordinate
Now, we can plug in the X and Y ordinates of PT, which is the point
we are testing to see if it lies in the extents. First extract the
X and Y ordinates from the point list:
(setq px (car PT)) ; get the X ordinate
py (cadr PT)) ; and the Y ordinate
)
And below is our composite test, where we perform a logical AND on
the result of two individual tests, requiring both of them to be
TRUE (X and Y) to result in AND returning T, which becomes the new
predicate's result:
(and (>= x-max px x-min)
(>= y-max py y-min)
)
So, only if both conditions are true will AND return T, indicating
that the subject point does lie on or within the rectangle. If the
first condition is NOT TRUE, then the result of AND cannot be TRUE,
so there is no need to evaluate the second condition. Now, we can
"shrink-wrap" our new predicate as a user-defined function:
; (windowp <point> <corner1> <corner2> )
;
; Returns NON-NIL if <point> lies on or within the rectangular
; extents defined by the <corner1> and <corner2>, two points on
; either diagonal of the subject rectangle.
(defun windowp (p c1 c2 / xmax xmin ymax ymin px py)
(setq xmax (max (car c1) (car c2)) ; high X
xmin (min (car c1) (car c2)) ; low X
ymax (max (cadr c1) (cadr c2)) ; high Y
ymin (min (cadr c1) (cadr c2)) ; low Y
px (car p) ; X and Y of point
py (cadr p) ; to examine.
)
(and (>= xmax px xmin) ; both conditions must be TRUE
(>= ymax py ymin) ; if the point lies in the extents.
) ; The result of AND becomes the
) ; result of WINDOWP.
Let's test our new predicate using a small command shell:
(defun C:INSIDE? (/ cor1 cor2 pt)
(if (and (setq cor1 (getpoint "\nOne corner: "))
(setq cor2 (getcorner cor1 "\nOther corner: "))
(setq pnt (getpoint "\nTest point: "))
)
(if (windowp pnt cor1 cor2)
(princ "\nPoint is on/within the specified extents.")
(princ "\nPoint is outside of the specified extents.")
)
(princ "\nInvalid, must supply three points.")
)
(princ)
)
Notice again, how the AND function is used to handle the user input,
so that if a null response is detected, the program will immediately
terminate without prompting for any remaining input.
The above function is written strictly with code modularity in mind
and not with effeciency in mind. So, if you needed to call WINDOWP
reiteratively, to for example, compare a set of many points to the
same rectangular extents, the above version of WINDOWP is really not
a very effecient way to do it.
The reason is simple: When you are using the function over and over,
you are passing it three arguments each time it is called, but two
of the three arguments always have the same values in each iteration.
This means there is a lot of needless argument passing taking place
along with computation involving these arguments within the body of
the called function. This can be dealt with by writing a simplified
version of of the function that doesn't get passed the coordinates of
the rectangular extents (since these are the arguments that would be
the same each time you call WINDOWP to compare many coordinates to
the same rectangular extents). Instead, you can write WINDOWP so it
only takes one argument, the point you are comparing to the extents,
and expects to find the values computed from the two points defining
the rectangle assigned to several free variables that are bound to
the caller's environment.
So, here is how you could re-write WINDOWP to be much more effecient
when it is called reiteratively with the same extents:
; (nwindowp <point> )
;
; A less-modular version of WINDOWP for reiterativly comparing
; many points to the same rectangular extents.
;
; Determines if the point <point> lies on or within the rectangular
; extents defined by the X and Y ordinates of two points, which are
; expected to be in the following FREE variables:
;
; WPXMIN: Lowest X ordinate of the two points on either diagonal.
; WPXMAX: Largest X ordinate... "" "" ""
; WPYMIN: Lowest Y ordinate...
; WPYMAX: Largest Y ordinate...
(defun nwindowp (pt)
(and (>= wpxmax (car pt) wpxmin)
(>= wpymin (cadr pt) wpxmax)
)
)
With this, in the environment from which NWINDOWP is being called,
put the extents where the function expects to find them. This need
only be done ONCE, regardless of how many times NWINDOWP is called on
to compare any number of points to the same rectangular extents:
...
(setq wpxmin (min (car p1) (car p2))
wpxmax (max (car p1) (car p2))
wpymin (min (cadr p1) (cadr p2))
wpymax (max (cadr p1) (cadr p2))
)
...
Now, assuming the symbol POINT_LIST is assigned to list of coordinates,
each of which must be compared to the extents, you could do something
like this to compare them to the rectangular extents:
(foreach point POINT_LIST
(if (nwindowp point)
(princ "\nPoint is inside extents")
(princ "\nPoint is outside extents")
)
)
The point here is to illustrate that writing functions to be modular
is not always in the interest of effeciency. This is demonstrated by
the fact that when any function like WINDOWP is called reteratively,
that it should not be passed arguments that produce the same value in
in the function body in every single iteration, and that you should
try to avoid doing computations in the body of the function when they
will produce the same result in every iteration. So, if you were to
treat the arguments that would carry the same values as "constants",
and instead assign them once outside of the body of the function that
you are calling reiteratively, it will result in more effecient code.
Back to logical operators.
Now, we'll examine another logical operation. Here we can also draw
an analogy to the conditional branching we do every day in our minds:
"If we catch our limit of fish, OR run out of bait,
then we can go home".
Here we have another case where conditional branching is dependant on
a CONNECTIVE (two conditions). But in this case, the OR operation is
implied, meaning simply that if any ONE of the two logical states is
TRUE, then the condition derived from a logical OR operation is TRUE.
Only if ALL input states are found to be FALSE, will the result of
the logical OR operation also be FALSE.
The AutoLISP OR function.
Syntax: (or <expr>...)
The OR function takes any number of arguments, and interprets them
logically. Arguments are evaluated left-to-right in the order they
are supplied. When the first argument that evaluates to a NON-NIL
value is encountered, all arguments proceeing it are ignored and do
not get evaluated. If a non-nil result is found, then OR immedately
suspends evaulation of additonal arguments and returns the symbol T,
representing a logical TRUE state.
So, we can use OR to permit a conditional function to decide if a
particular branch of code is to be evaluated if any ONE of several
logical states is TRUE.
Some example calls to OR, and their results:
Expression Result
--------------------------------
(or T t t nil t) T
^
(or NIL NIL T nil t) T
^
(or NIL NIL NIL) nil
^
(or T nil nil nil) T
^
(or NIL 0 nil t) T
^
(or NIL NIL "FOO" t) T
^
Like AND, OR is also a special form that does conditional evaluation.
In the above examples a caret (^) appears under each expression to
show the point where evaluation would stop given those values. The
caret points to the last argument that would need to be evaluated,
whose result is what determines the outcome of the OR expression.
All values to the right of the caret would never be evaluated under
any circumstances.
Here is a very simple example of how you can use OR to determine if
a given value is equal to one of several possible values.
; get a character from the user, must be either of the three shown:
(setq ax (getstring "\nEnter X, Y, or Z: ))
; you are interested in the upper-case conversion of only the
; first character entered:
(setq ax (strcase (substr ax 1 1)))
; See if the user entered one of the three valid characters,
; then proceed accordingly:
(if (or (= ax "X")
(= ax "Y")
(= ax "Z")
)
(princ "\nOK!")
(princ "\nNo good, must enter X, Y, or Z!!!")
)
The message "OK!" will be printed if the value in AX is either one
of the three characters "X", "Y", OR "Z".
This is usually how the typical multiple case/value comparison might
be coded in another language, using a logical OR operator. There is
nothing wrong with doing it this way, but with LISP, this operation
can also be performed another way, much more effeciently than doing a
logical OR of multiple equality comparisons as shown above. Instead,
it can be done using the MEMBER function:
(member <expr> <list> )
If <expr> appears in <list> (using EQUAL as a comparison test), then
MEMBER will return the portion of <list> beginnning with the first
occurance of <expr>.
In contrast to the above example using OR and EQUAL:
(if (member ax '("X" "Y" "Z")) ; value in AX appear in the list?
(princ "\nOK!")
(princ "\nNo good, must enter X, Y, OR Z.") ; no
)
In general, assuming each instance of <testvalue> is the same:
(or (equal <testvalue> <value1> )
(equal <testvalue> <value2> )
(equal <testvalue> <valueN> )...
)
Could be written more effeciently as:
(member <testvalue> (list <value1> <value2> <valueN>...) )
In the latter case of MEMBER, the greater effeciency is mainly due to
the fact that the <testvalue> is a constant, which is not the case in
the multiple OR-EQUAL tests. So in LISP, the OR function may not be
the best way to do a multiple CASE/VALUE selection.
So what can we do with OR in LISP?
We will now create a predicate function that can be used to determine
if two rectangular extents intersect each other.
Here are two theoretical TRUE/FALSE examples illustrated graphically:
+-------------+p2
| |
| |x2
| +-----+-----+p4
| | | |
|p1 |x1 | | <---- Two rectangles intersect, so the
+-------+-----+ | predicate should return "TRUE".
| |
|p3 |
+-----------+
+-------------+p2
| |
| | +---+p4
|p1 | | | <---- Two rectangles don't intersect, so
+-------------+ | | the predicate should return "FALSE".
| |
p3+---+
Notice that we are building on the WINDOWP predicate developed as a
part of our discussion on the AND function, because we can determine
if these two rectangles intersect, by determining if any ONE of the
four corners of either rectangle is within the extents of the other.
There are more effecient ways of doing this, but this one was chosen
because of the extensive use of predicates and logical operators, for
the sake of illustration.
; (rxint p1 p2 p3 p4)
;
; Determines if two given rectangular extents intersect, where
; both must be orthogonal to the X and Y axes of the coordinate
; system in which their defining points are expressed.
;
; Arguments: P1 and P2: diagonal corners of first rectangle.
; P3 and P4: diagonal corners of second rectangle.
;
; Returns NON-NIL (T) if the rectangles intersect, NIL otherwise.
;
; Requires WINDOWP, and NWINDOWP predicate functions above.
(defun rxint (p1 p2 p3 p4 / wpxmin wpxmax wpymin wpymax p5 p6)
; First, since we may have to compare up to four coordinates
; to the same rectangular extents, we know from the discussion
; above that it would be more effecient to not have to pass the
; rectangle corner arguments each time we call the predicate.
; So we will call the NWINDOWP function for all but one test.
;
; The first thing we must do is set up the values that NWINDOWP
; requires and assign them to the variables which the function
; expects them to be in when it is called:
(setq wpxmin (min (car p1) (car p2)) ; NWINDOWP expects two
wpxmax (max (car p1) (car p2)) ; X and Y ordinates to
wpymin (min (cadr p1) (cadr p2)) ; be sorted and stored
wpymax (max (cadr p1) (cadr p2)) ; in WPxxxx variables.
)
; We also need the other two corners of the second rectangle,
; which we can get by cross-pairing the X and Y ordinates of
; the two known corners:
(setq p5 (list (car p3) (cadr p4))
P6 (list (car p4) (cadr p3))
)
; Now we're ready to comparare the two rectangles to each other.
; The first thing we must do is call WINDOWP once, to compare
; any corner of the first rectangle to the second rectangle.
; This must be done to detect or eliminate the possiblity that
; the first rectangle lies entirely within the second one, which
; cannot be detected by the last four corner/extents comparisons.
;
; If the WINDOWP predicate is false, then we know that the first
; rectangle does not lie within the second, and if that is the
; case, all that remains for us to do is compare the four corners
; of the second rectangle to the first one, to satisfy all other
; cases of a possible intersection.
;
; We perform a logical OR on the result of all tests, because in
; this case, if any one of them is successful, then the result
; of the RXINT predicate is known, and no further evaluation is
; necessary:
(or (windowp p1 p3 p4) ; If rectangle 1 is within rectangle 2,
(nwindowp p3) ; OR, if any one corner of rectangle 2
(nwindowp p4) ; is within rectangle 1, then return T
(nwindowp p5) ; to indicate that the two rectangles
(nwindowp p6) ; intersect.
)
) ; OR is the last expression in the body
; of RXINT, so its result becomesthe
; result of RXINT.
Excercise:
Try writing a small AutoCAD command (C:FUNCTION) to test the (RXINT)
function. It should prompt for two corners of two rectangles (four
points using GETPOINT and GETCORNER), and call RXINT passing it the
four corner points. Then, using IF to branch on the result of RXINT,
print out a message indicating the outcome of the predicate test.
Advanced excercise:
Re-write the RXINT function so that it returns a more useful result
if an intersection is found. That result is the two corners of the
intersection of the two input rectangles, or the rectangular area
that lies within BOTH rectangles that the RXINT predicate compares.
The first graphic illustration above shows two such points: x1 and
x2. In a case where one rectangle is entirely within the other,
RXINT should return the two diagonal corners of the inner rectangle.
Note: Save all the functions that appear in this tutorial, as they
may be used extensively in future installments for exercises,
in which we will write more useful programs using them.
Now, we move on, to the third and last of the three basic logical
operators: NOT.
Lets go back again, to the conditional used in Lesson 8:
"If it is sunny today, I will leave my
umbrella home."
Now, consider this:
"If it is NOT raining today, I will leave
my umbrella home."
The precedents in both of the above statements are roughly equal, or
they mean roughly the same thing (really they don't but we'll use a
more precise example below).
The AutoLISP NOT function.
The logical NOT operator is the simplest of the three to understand,
because it takes only one logical state as input, and produces only
one logical state as ouput. And, since there are only two logical
states, take a guess at what NOT does to its input state?
Right - The output state of a logical NOT is simply the complement of
the input state. In simpler terms, NOT inverses its input, so that
if the input state is TRUE (any NON-NIL value), then the output state
would be FALSE (always NIL), and if the input state is NIL, then the
result of NOT is the symbol T.
Examples of NOT:
Expression Result
-----------------------
(not T) NIL T is a non-nil value
(not nil) T
(not "foo") NIL "foo", 0, and 1 are all non-nil values
(not 0) NIL
(not 1) NIL
In the discussion above involving the EVENP predicate, we said that
we could use one of the logical operators with EVENP to easily write
the INVERSE predicate ODDP. Here's how:
Axiom: Anything that is NOT FALSE, is TRUE.
So, what axiom could one apply to any number that is ODD?
Axiom: Any number that is ODD, is NOT EVEN.
And so it follows:
(defun ODDP (num)
(not (evenp num)) ; return T if <num> is NOT an even number
)
And in actual operation, (oddp 5) would call (evenp 5), which would
then return NIL to NOT, which indicates FALSE. In-turn, NOT would
return the complement of NIL, which is T (for TRUE).
The main reason that the NOT logical operator was saved for last, is
because it often will appear in conjunction with the other two basic
logical operators, AND and OR, to abstract much more complex logical
operations, as we will see below.
The XOR logical operator.
The XOR logical operator is the "Exclusive OR" operator. XOR works
like this: It takes two logical states as INPUT, and produces one
logical result. If ONLY ONE input state is TRUE, and the other is
FALSE, then the output is TRUE. If the two input states are either
both TRUE, or both FALSE, then the resulting output state is FALSE.
Expression Result
-----------------------
(xor T T) NIL
(xor NIL T) T
(xor NIL NIL) NIL
(xor "moo" NIL) T
(xor T NIL) T
(xor T 0) NIL
AutoLISP does not provide a primitive XOR operator (that is, not as
it applies to SYMBOLIC LOGIC, but there is a way to perform logical
XOR operations on binary or "bitwise" logical values in numbers and
we will look at these in a future installment).
This is no problem since we can easily define our own XOR using a
slightly complex connective:
(defun XOR (a b)
(and (or a b) ; IF A and B complement each
(not (and a b)) ; other, then return T (true).
)
)
As you can see, the complexity of composite logical operations can
become very involved and confusing. You will encounter situations
where you will have to combine several of the basic logical operators
to perform composite logical operations on multiple operands.
You'll find that adding the XOR function to your AutoLISP function
library can help to reduce the complexity of some of those composite
logical operations, and make the logic and intent of your programs
clearer.
Improper use of PREDICATES and LOGICAL OPERATORS.
Just about everyone who ever coded in LISP has used double-negatives
in their conditional branching tests at one point or another. Here
is an example of a double-negative test expression in an IF function:
(setq x <expr> )
(if (not (null x)) ; <--------------- a double negative
(princ "\nThe value of X is NOT NULL")
(princ "\nThe value of X is NULL")
)
The above test expression is called a double-negative, because both
NOT and NULL negate (or logically invert) their arguments. And that
means that the logical value of any argument to the above must always
be identical to the result of (not (null x)), making this operation
entirely superflorous.
The (not (null x)) in the above expression is exactly the same as
doing this:
(if x (princ "\nThe value of X is NON-NIL")
(princ "\nX is NIL")
)
In otherwords, both given the same value for any <expr>:
<expr> = (not (null <expr> ))
So, the LOGICAL VALUE of any <expr> is the same as the logical value
of (not (null <expr>)) given the same <expr> value in both cases.
Here's why: NULL is identical to NOT. They are the exact same two
functions that perform the exact same operation. And so what one is
really doing by using (not (null <expr>)) is:
(not (not <expr> )) ; NOT == NULL == NOT
Which obviously doesn't make any sense at all.
Here is another common mistake:
(if (/= <expr> NIL)
(princ "\n<expr> is TRUE")
)
This should written more simply as:
(if <expr> (princ "\n<expr> is TRUE"))
And here, just as in the previous case, the logical value of <expr>
when it appears in a conditional, will always be identical to the
logical value of (/= <expr> nil)
The easiest way to understand this is to understand exactly what the
IF function, and all other conditional functions are doing internally
to determine what branch to take. The IF function simply looks at
its FIRST argument (the <test> expression), and determines if it is
equal to NIL, nothing more. If doens't care what kind of value you
give it, it only cares whether the the value is Nil. If it is, then
the second expression in the IF function is skipped, and if a third
expression is present, it is evaluated.
Here is another similar example that you might see often:
(if (= NIL <expr> )
(princ "\n<expr> is FALSE")
)
Which should be written as:
(if (not <expr> )
(princ "\n<expr> is FALSE")
)
These kinds of syntactical errors can actaully be attributed to not
fully understanding precisely how symbolic logic works. In general,
a test expression in an IF, or other branching function should never
contain an equality test that attempts to compare some other value to
NIL or T. Any test of this nature is almost entirely superflorus.
BOUNDP is a predicate that should be avoided.
In AutoLISP, there is no UNBOUND variable (a bound variable is any
symbol that is known (or "bound") to the environment, and even if a
symbol is bound to NIL, it is still BOUND). In fact, BOUNDP serves
no real purpose in AutoLISP, and in the other dialects of LISP, its
meaning is very different.
Here is how BOUNDP will often be (mis)used in AutoLISP:
(if (boundp <expr> )
(princ "\n<expr> is not BOUND")
)
Not true, in the strictest sense, because "not bound" means that a
symbol is unknown, and therefore has NO value at all, not even the
"value" NIL.
Again, the above use of BOUNDP is precisely the same as:
(if <expr>
(princ "\n<expr> is NON-NIL")
)
And so, the logical values of (bound <expr> ) and just <expr> are
also precisely identical.
Other conditional branching functions.
The COND function.
COND, is like DO/CASE in other languages. It permits a program to
select one of many possible branches to take depending on the result
of one of many tests, where each test is associated with one branch.
In reality, COND is the fundamental conditional branching function in
LISP, more basic than IF (which by the way, is very seldom used by
professional COMMON LISP programmers). That is not to suggest that
you should not use IF, especially if it will make the logic of your
program more obvious to you and perhaps others, but there some good
reasons why COND is used more often, which we will examine in detail
later in this installment.
The general syntax of COND:
(cond ( <test1> <result> <result> <result>... )
( <test2> <result> <result>... )
( <testN> <result>... )
...
)
Each <test> is a test expression, just like the first argument to the
IF function is. COND evaluates each <test> in the order they appear
until it finds one that evaluates to NON-NIL. Then, every <result>
expression which immediately proceeds the successful <test> will be
evaluated, left to right.
Any number of <result> expressions can be associated with each <test>,
and all of them will be evaluated if thier <test> results in NON-NIL.
COND will always return the result of the last <result> expression it
evaluates. Each <test> expression and all <result> expressions which
immediately proceed it are referred to as a CLAUSE. There can by any
number of clauses in a single COND expression.
If no <test> expression in a COND evaluates to a NON-NIL value, then
COND will return NIL, and no <result> expressions will be evaluated.
If a <test> expression has no <result> expressions associated with it,
then the NON-NIL result of the succesful <test> expression itself is
returned by COND.
A "default" clause can be supplied by placing it as the LAST clause
in the expression, and supplying a literal NON-NIL value in place of
the "test" (usually the symbol T is used as the default clause test
value, but any non-nil value would have precisely the same effect).
Here is a simple example of how you can use COND to select a value
from several possible ones depending on which test is successful.
The function takes an integer from 1 to 5, and returns the English
equivalent:
(defun say-digit (n)
(cond ( (minsup n) (princ "\nNo negative values!"))
( (zerop n) (princ "zero"))
( (eq n 1) (princ "one"))
( (eq n 2) (princ "two"))
( (eq n 3) (princ "three"))
( (eq n 4) (princ "four"))
( (eq n 5) (princ "five"))
(t (princ "Sorry, I can't count that high"))
)
(princ)
)
This COND expression has EIGHT clauses, with the last one being a
DEFAULT clause. If none of the (eq..) test expressions evaluates
to a NON-NIL result, then the remaining expressions in the default
clause will be evaluated.
In the above examplle, each clause has only one <result> expression,
but as we will see below, each could have any number of <result>'s.
Here is the sequence of expressions that are evaluated if the value
supplied to SAY-DIGIT were 3:
Expression Result
-----------------------
(minsup n) Nil
(zerop n) Nil
(eq n 1) Nil
(eq n 2) Nil
(eq n 3) T <---- COND stops evaluating <test>'s
(princ "three") "three" and starts evaluating <result>'s
Enter SAY-DIGIT into your text editor, load it, and give it a try:
Command: (say-digit 3)
"three"
Command: (say-digit 1)
"one"
Command: (say-digit 4)
"four"
Command: (say-digit 8)
"Sorry, I can't count that high"
Command:
This should make the basic function of COND clear. And, note how the
default case is evaluated when no test is successful. COND is also a
SPECIAL FORM that does CONDITIONAL EVALUATION. It will evaluate only
to the first test expression that results in a non-nil value and will
not evaluate any part of any proceeding clauses. We will also learn
about a more effecient way of performing an operation like this using
another AutoLISP function called ASSOC, in a future installment.
One very commonplace use of COND, is to branch on a series of tests
that are performed on a value input by the user, where a command may
give a user a choice of several options or "commands", where each of
these options cause a different action to be taken by the program.
In the following example, we will write a TINY RPN calculator that
will let you enter numeric values (or distances) and operators right
on the command line, and have the results displayed. We'll be using
some functions that you might not have used before, or may not be
familiar with. Don't worry about them for now, the purpose here is
simply to show the COND function in action. But you might find this
TINY RPN calculator to be very useful with AutoCAD, so enter it into
your text editor, and give it a try!
; First, we need a subroutine to centralize input and display
; the current total. This will also help to make it easier
; to understand the logic of the entire program.
;
; (GETCALC) sets up the keywords that are accepted as options
; to distance input, then displays the current total on the
; command line, and waits for the user to enter a response,
; which can be a numeric operand to be operated on, a numeric
; operator (e.g,. +, -, *), or a command that causes something
; else to happen. The COND function below is responsible for
; deciding what is to happen, depending on what value the user
; supplies on each input.
(defun getcalc ()
(initget 1 "+ - * / Q C") ; initialize keywords,
(getdist (strcat "\n" (rtos total) ; display current total
" " ; and operator, and get
operator ; input from the user
" "
)
)
)
; C:CALC - A TINY command-line calculator for AutoCAD.
;
; Bound variables:
;
; INPUT User input (a real number or option keyword)
; TOTAL The current calculator register total
; OPERATOR The current numeric operator (a string)
;
; Free variables:
;
; *TOTAL* The running total from the last use of C:CALC
; which automatically carries over to the next
; invocation.
(defun C:CALC ( / input total operator)
; initialize operator and total (use total
; from last use of program if it exists).
(setq operator "+" ; start with addition
total (cond (*total*) (t 0.0)) ; use 0.0 for default
)
; say hello
(princ (strcat "\nCadCalc v1.0 - Enter operator or operand,"
" <C>lear or <Q>uit.\n"))
; Enter the input/processing loop, and continue while the
; user doesn't enter "Q" to quit:
(while (/= "Q" (setq input (getcalc))) ; while user doesn't
; enter "Q",
; branch on each possible INPUT value and dispatch
; to the appropriate case:
(cond ( (member input ; Did the user
'("+" "-" "*" "/") ; enter one of
) ; these operators?
(setq operator input) ; yes, assign it
) ; to OPERATOR.
( (eq "C" input) ; clear total?
(setq total 0.0) ; yes, do it.
)
( (numberp input) ; number entered?
(setq total ; yes: perform the
(apply (read operator) ; operation and
(list total input))) ; make the result
; the new total.
)
(t (princ "\nInvalid response.")) ; do this in
) ; case something
) ; falls thru.
; Save the current total for next time and exit.
; (use "!*total* to supply last total to AutoCAD input)
(setq *total* total)
(princ)
)
You can add new operators (binary and unary) to this calculator very
easily. Each new function would require another clause in the COND
expression that dispatches each value entered by the user. Here are
some examples of some unary operators that you could add:
Unary operators: Square Square root
Truncate Reciprocal
Negate Logarithim
Sin/Cos/Tan Pi (constant)
Note that all existing operators in the calculator are binary, and
operate on both the current total, and input numeric values. If you
were to add these unary operators, they should operate on only the
current total. So, you must retain the last binary operator used,
since it is always the default operator used on the current total,
and any numeric value entered. All unary operators could be handled
by a single clause in the COND, if they are the same as the names of
their conterpart AutoLISP symbols.
Advanced excercise:
Add the following MEMORY functions to the calculator:
MR Copies the current value from the memory registor
to be used as an operand.
MC Clears (zeros) value in the memory registor.
Mx Performs operation "x" on the current total and
current memory registor value, and updates the
memory registor value with the result, where x
is any valid operator, e.g.,
M+ add current total to memory
M- subtract current total from memory
M* multiply memory by current total
M/ divide memory by current total, etc.
More on conditional branching.
IF verses COND: Which is better?
COND has several advantages over IF. A single COND expression can do
the job of many nested IF-THEN-ELSE-IF expressions more effeciently.
The following example uses several nested IF expressions where one
COND could be used to do the same job:
; FLIST command: List a file on the text display.
;
; fn = filename string (input by user)
; fh = handle of opened file
; l = each line of the file (a string)
(defun C:FLIST ( / fn fh l)
(setq fn (getstring "\nEnter filename to LIST: ")) ; get filename
(if (/= fn "") ; IF filename /= ""
(if (setq fh (open fn "r")) ; THEN IF file exists
(progn (textscr) ; THEN goto textscr,
(while (setq line (read-line fh)) ; read each line,
(write-line line) ; and print each line,
)
(close fh) ; and close file.
)
(princ "\nCan't open file for input.") ; ELSE (2nd IF)
)
(princ "\nInvalid, must enter filename.") ; ELSE (1st IF)
)
(princ)
)
The PROGN function.
(progn <expr1> <exprN>...)
PROGN is a control function that permits any number of expressions to
appear where only ONE expression is expected or permitted. PROGN is
frequently used within an (IF) conditional expression to permit more
than one result expression to be evaluated in the case of a success
and/or failure. The result of PROGN itself, is the result of its last
expression or argument.
In the above example, there are three actions that must be taken if
the file is succesfully opened:
1. The program must switch to the text screen.
2. Each line must be read, and printed on the display.
3. The file must be closed.
The IF function only accepts three arguments; the test expression,
one expression to evaluate if the test expression is successful, and
optionally, one expression to evaluate if the test expression fails.
Since the above three actions cannot be accomplished by evaluating
just one expression, they can't appear as a single result expression
(well they really can, but we need to arrange for it explicitly).
So, in order to evaluate all of the expressions needed to perform the
three actions above if the result of the test expression is NON-NIL
(if the file was opened for input), all expressions that are to be
evaluated can appear as arguments to the PROGN function. That is,
they are nested within in a single expression, which satisfies the
requirement of IF, that there only be one result expression that is
to be evaluated if the test is a success.
Here is another simple example. This function swaps the two values
assigned to two symbols if the first one is greater than the second:
(defun swap (s1 s2)
(setq v1 (eval s1)
v2 (eval s2)
)
(if (> v1 v2)
(progn (set s1 v2) ; both SET expressions must be
(set s2 v1) ; evaluated sequentially, if the
) ; test: (> v1 v2) is TRUE.
)
)
You will find that multiple PROGN's within calls to (IF) are a major
source of mismatched parenthesis. Also, PROGN's within IF's can make
revisions to code more difficult. In fact, using IF instead of COND
can itself be a source of frustration whenever additional test/result
branches must be added to a construct, which would require nested IF's
to avoid major surgery or using a COND, and nesting IF expressions is
a practice that should be avoided whenever possible.
Consider the following version of the FLIST program above, which uses
ONE COND expression instead of TWO IF expressions and a PROGN:
(defun C:FLIST ( / fn fh l)
(setq fn (getstring "\nEnter filespec to LIST: ")) ; get filename
(cond
( (= fn "") ; CR pressed?
(princ "\nInvalid, must enter a filespec.") ; say so and exit
)
( (not (setq fh (open fn "r"))) ; file NOT open?
(princ "\nCan't open specified file for input.") ; say so and exit
)
(t (textscr) ; ELSE, proceed
(while (setq l (read-line fh)) ; as planned.
(write-line l)
)
(close fh)
)
)
(princ)
)
What is important here, is to remember that COND can help to reduce
the depth of nested expressions in contrast to IF, and it should be
apparent by comparing the two versions of FLIST, that using COND can
make program logic flow more obvious.
Note that in the former example which uses IF/PROGN, the two failure
result expressions of the two IF constructs (the calls to PRINC) are
not even located near the test expressions whose result triggers them,
and this is what makes it difficult to follow the logic of a program.
So, we can see at least one reason why using COND instead of IF can
make life easier for us. The other reason is due to the fact that
each COND clause can contain any number of result expressions that
are evaluated when thier associated test is true and hence, there
is no need to have to nest multiple IF-THEN/ELSE result expressions
within a PROGN.
Formatting progam code.
What also helps make your programs logic clearer, is how you format
it. Formatting is not very important to the machine, but it is very
important to make your code easy for you to understand. Everyone
has their own way of formatting code, and there is really no right
or wrong way to do it, more important is that you will be able to
easily follow the logic of your own programs, if your formatting is
always consistent and adheres to a few basic rules.
If you are trying to follow code written by someone else who uses a
different formatting style, you may find it much easier if you just
reformat the entire program in accordance to your own style.
More conditional branching? Yes. We have yet to be introduced to
one fundamental AutoLISP conditional branching function: WHILE.
WHILE is considered to be a conditional branching function. It uses
a test to determine if (and how many times) it should evaluate an
unlimited number of result expressions. However, we will not cover
WHILE in this installment. Instead, we will explore it in detail in
the next installment which will deal with the subjects of reterative
processing loops and list processing.
Review:
In this installment, we learned:
o LISP uses Symbolic logic in conditional branching and differs
somewhat from other languages in this sense.
o In LISP, a value of 0 represents TRUE when used as the TEST in
any conditional branching construct.
o In LISP, all values have a logical value, which is interpreted
by conditional functions and logical operators.
o Except for the special symbol NIL, all values have the logical
value of TRUE. Nil has a logical value of FALSE.
o A predicate is a function that can perform a test and supply
the logical result to its caller, or just "answer a question".
o Predicates can help conditional branching functions select a
path of execution.
o Predicates always return NIL to indicate a failure.
o Predicates can return values other than T to indicate success.
o New predicate functions can be easily defined.
o AND, OR and NOT are the three BASIC logical operators.
o Logical operators can be used to combine the results of several
logical values produced by predicate functions into more complex
predicates which also produce logical values.
o The XOR logical operator can be defined using AND, OR, and NOT;
and can be used to reduce the complexity of composite logical
operations.
o The AND function can be used to validate user input as it is
obtained, and abort a program as soon as an invalid response
is entered or detected without letting a program needlessly
continue to a "dead end".
o The MEMBER function can be used to do the job that the logical
OR operator and multiple equality tests might be used to do in
other languages, more effeciently than the equivalent in LISP.
o Writing modular functions makes your code more re-useable, but
it is not always effecient.
o The inverse (or complement) to any predicate function can be
defined by applying the logical NOT operator to the result
of the predicate.
o The PROGN function permits multiple success and/or failure
result expressions to be evaluated within an IF expression.
o COND is like CASE or DO CASE in other languages, and can be
used to select from many possible paths of execution, or to
return many results, depending on the result of one of many
tests, each of which is associated with an execution path
or value to be returned.
o Using the COND function instead of nested IF functions and
PROGN's can often make a program much easier to revise, and
make a program's logic flow more obvious.
o The AND, OR, and COND functions are all special forms that do
conditional evaluation.
o Consistent formatting conventions are an important part of
ensuring that the logic of our programs is understandable.
Quiz:
1. If a language uses symbolic logic, then what does the integer
value 0 represent when it appears as the test or determinant
condition in a branching construct?
2. In each of the following (IF) expressions, circle the result
expression that will be evaluated, given the arguments shown:
(if 1 (princ "false") (princ "true"))
(if "" (princ "true") (princ "false"))
(if (and 0 1) (princ "Nope") (princ "Why not?"))
(if (not 1) (princ "T") (princ "NIL"))
(if 0 (princ "One") (princ "Zero"))
(if (member "Y" (list "x" "y" "z"))
(princ "It's in there!")
(princ "Nope, it's not there")
)
(if (and (or (not 1) nil)
(and (equal nil (not t))
(not nil)
)
)
(princ "TRUE")
(princ "FALSE")
)
3. From the following list of function names:
a. Circle all of those which are built-in predicates:
b. Cross out any predicate function that does not return
the symbol T to indicate success (or "TRUE").
ATOM FINDFILE /= WHILE BOOLEANP
BOOLE READ NUMBERP ZEROP POSITIVEP
LISTP 1+ EQUAL LOG LOSTP
MEMBER ABS LIST ~ ANGLEP
INTERS > LOGAND LSH FOUNDP
4. In LISP, what is the easiest way to determine if a given value
is EQUAL to any one of several 'candidate' test values.
5. What is the easiest way to extract a portion of an existing
list, where it begins with a specified element.
6. The AND function stops evaluating its arguments as soon as it
encounters the first:
a. NIL result
b. NON-NIL result
c. result that produces the symbol `T'
7. The OR function stops evaluating its arguments as soon as it
encounters the first:
a. NIL result
b. result that produces the symbol `T'
c. NON-NIL result
8. What does the NOT function do to its argument?
9. Given the following assignments, what is the result of each of
the proceeding expressions:
Assume: (setq x 1 y 0 z nil s "moo" n 25.0 )
(or (not x) (and x (or (not y) x)))
(and (not x) (or (not s) (and x y s z)))
(or s (or z n) (and x y))
10. Write the predicate function POSITIVEP, which is to return T if
its argument is 0 or positive, and NIL otherwise. You may NOT
use a predicate function in the definition of POSITIVEP.
11. Write a function called SLOPE, that takes two 2D coordinates,
and returns the analytical slope of a line between the points.
If the line is vertical then SLOPE should return NIL. If the
line is horizontal, then SLOPE must return 0.0
12. Using the function SLOPE, write the predicate COLNRP, which
takes three 2D points and returns T if the three points are
collinear, and NIL otherwise.
13. What conditional function can be used to avoid writing several
nested IF expressions or IF expressions containing PROGN's?
14. Assuming: (setq x 1) (setq s "string")
(setq y 0) (setq m nil)
(setq z nil) (setq l (list "1" "2" "3"))
In the calls to the special forms below:
a. Circle the last expression in each AND and OR operation
that would have to be evaluated to determine the outcome
of the expression.
b. Indicate what the result of each expression is.
c. In the COND function, circle the last expression to be
evaluated, whose result becomes the result of the COND.
(or nil m t nil s)
(and z nil l nil x)
(or (not y) (or (and x "foo") nil l))
(or m nil z y nil x)
(cond ( (eq x 2)
(princ "first clause is true")
(princ "first clause, second result")
)
( (not (listp l))
(princ "second clause is true")
)
( (not y)
(princ "third clause is true")
(princ "result 2")
(princ "result 3")
)
(m (princ "fourth clause is true"))
( (eq (type s 'string))
(princ "fifth clause is true")
(princ "result expression 2")
)
(t (princ "default result 1")
(princ "default result 2")
)
)
15. What is an UNBOUND SYMBOL?
----------------- END of Lesson 9. -------------------
In lesson 10:
Program control:
Reiterative processing loops.
Reiterative LIST processing.
The AutoLISP STACK and recursion.