In some cases it will be convenient to define new predicates of which the system lacks. For example, we mentioned that list processing functions are usually not applicable to dotted pairs. It would be convenient to have a
predicate that allowed us to verify this condition. AutoLISP provides the listp
predicate, which checks if an object is a list. But listp returns T also if the argument is nil or if it is a dotted pair. Visual LISP3 provides the vl-consp
predicate that will return T only if the list is not empty, but does not distinguish between proper lists and dotted pairs. The proof that the list is a dotted pair would be that its cdr was an atom instead of a list. The fact that an object is an atom is checked by the predicate atom. So to discern whether an object is a dotted pair we must combine the predicates vl-consp and atom.
To combine predicates have the special forms and and or. The logical
operators and and or allow us to build complex predicates, in which several conditions are tested.
and returns false (nil) in case any of its arguments returns nil. When a
nil value is found the evaluation stops.
or will return true (T) in case any of its arguments returns a value other than nil. When a value other than nil is returned the evaluation of its arguments will stop and T will be returned.
Grouping the conditions that the object is a list, that it is not nil and that its cdr
is an atom we could verify if the argument is a dotted pair:
(defun dotted-pair-p (arg)
(and (vl-consp arg) (cdr arg) (atom (cdr arg)))) Listing 5.3. DOTTED-PAIR-P predicate.
In the name chosen for this predicate we have adopted the convention to finish it with the letter p although, as seen in atom this rule has its exceptions.
Other useful predicates are those that check the data type. AutoLISP provides some but not all. To check the data type we use type. The following function checks if the argument passed to it is a string 4.
(defun stringp (arg) (eq (type arg) 'STR))
Listing 5.4. STRINGP predicate.
The equality predicate to use in this case would be eq because we would be
checking that the symbol returned by type is the same as STR the symbol identifying the character string type. In the same way we could define, if necessary, predicates for other data types.
_$ (stringp "abc") T
_$ (stringp 'abc) nil
_$
Conditionals
Decision making is a fundamental part of any program. Conditionals are specialized functions that allow the program to act one way or the other according to the result of an expression which acts as predicate. As we can build our own predicate functions we can write functions that make decisions implying any degree of complexity.
The conditional IF
The simplest LISP conditional is if. It takes three arguments:
the predicate that returns true or false,
the expression which is executed if the predicate has returned true, and the expression that is executed in case false is returned.
The third argument may be omitted, not the first nor the second ones. In case the third argument is omitted, if will return nil when the predicate returns false.
The following function prints a message saying if the argument it receives is or is not a list:
(defun is-list? (arg / result) (if (listp arg)
(setq result "Yes, it's") (setq result "No, it's not")) (princ (strcat result " a list")) (princ))
Listing 5.5. IS-LIST? function with local variables.
If the value of arg is a list, the value saved in the local variable result is
"Yes, it's". In case it is not, that variable takes the value "No, it is not". Then we concatenate that value with the phrase " a list", the resulting string
being printed in the console by princ.
The last princ with no arguments prevents displaying the value returned by the previous princ. This is a standard procedure used so programs end silently without printing in the Console (or in the command line) the value returned by their last expression.
We mentioned before that in the functional programming style our job should not be considered finished without analyzing how much can we do without setq. For the function above this would lead to a new version of is-list? (See Listing 5.6) where everything is done without the use of local variables.
(defun is-list? (arg)
(princ (strcat (if (listp arg) "Yes, it's"
"No, it's not") " a list"))
(princ))
Listing 5.6. IS-LIST? function without local variables.
The if function is another of LISP’s special forms. Its purpose is precisely to leave one of its arguments unevaluated, contrary to the general evaluation rule we have stated. It will often be necessary to evaluate within one of its branches more than one expression. In these cases it will be necessary to group the
expressions for each of the alternatives as a single block. The AutoLISP function to create a block of code is the special form progn. The expressions within the block are evaluated successively, returning the value output from the last one. Compare the results displayed in the console from three separate expressions:
_$ (setq x "returned")(setq y 'value)(setq z 1)(list z y x)
"returned"
VALUE 1
(1 VALUE "returned") _$
with what we obtain from the same three expressions within a progn block.
_$ (progn (setq x "returned")(setq y 'value)(setq z 1)(list z y x)) (1 VALUE "returned")
_$
The COND conditional
With progn we are explicitly creating a block. Many LISP functions implicitly create blocks. Among them we have cond which, among the advantages offered is that of eliminating the need to use progn. It also allows checking multiple conditions, compared to the single test expression that if supports, thus making it far more general in its application. The expressions that will be evaluated in case one of its test expressions returns true will return their result as if they were included in a progn expression.
cond admits zero or more arguments (which we call clauses), which should be lists in which the first item will be used as the predicate expression followed by zero or more expressions.
(cond
((predicate-1)(expression)…) ((predicate-2)( expression)…)
…)
In a cond expression each of the predicates is evaluated in the order they appear until one returning true is found. When this happens, the expressions following this predicate are evaluated in succession to return the last expression’s value.
No more clauses are evaluated. If none of the predicates returns true, cond
returns nil. An example is the function type? (Listing 5.7).
(defun type? (arg)
(cond ((listp arg) (princ arg) (princ ": a list"))
((vl-symbolp arg) (princ arg) (princ ": a symbol"))
Frequently the symbol T is used as the predicate for a last clause, so if none of the previous ones return true, this clause will always be evaluated. The
following function prints a message with the type of some data. If the tests fail to define the data type, it will report this fact as part of this final clause, which is evaluated by default whenever none of the test expressions returns true. To check whether this is zero or a negative number two predicates are grouped in an and expression.
As when the first expression is false the second expression is not evaluated this will avoid the error that passing a non-numeric argument to zerop or minusp
would produce. To print the messages princ is used since it is capable of printing on the console any type of data and it does not introduce line breaks.
_$ (type? '(a b))
-1 is a negative number _$ (type? 1)
1 is a positive number _$ (type? +)
#<SUBR @0000000030b32ba8 +> is an unknown type _$