Common Lisp the Language, 2nd Edition


next up previous contents index
Next: Blocks and Exits Up: Control Structure Previous: Establishing New Variable

7.6. Conditionals

The traditional conditional construct in Lisp is cond. However, if is much simpler and is directly comparable to conditional constructs in other programming languages, so it is considered to be primitive in Common Lisp and is described first. Common Lisp also provides the dispatching constructs case and typecase, which are often more convenient than cond.


[Special Form]
if test then [else]

The if special form corresponds to the if-then-else construct found in most algebraic programming languages. First the form test is evaluated. If the result is not nil, then the form then is selected; otherwise the form else is selected. Whichever form is selected is then evaluated, and if returns whatever is returned by evaluation of the selected form.

(if test then else) == (cond (test then) (t else))

but if is considered more readable in some situations.

The else form may be omitted, in which case if the value of test is nil then nothing is done and the value of the if form is nil. If the value of the if form is important in this situation, then the and construct may be stylistically preferable, depending on the context. If the value is not important, but only the effect, then the when construct may be stylistically preferable.


[Macro]
when test {form}*

(when test form1 form2 ... ) first evaluates test. If the result is nil, then no form is evaluated, and nil is returned. Otherwise the forms constitute an implicit progn and are evaluated sequentially from left to right, and the value of the last one is returned.

(when p a b c) == (and p (progn a b c)) 
(when p a b c) == (cond (p a b c)) 
(when p a b c) == (if p (progn a b c) nil) 
(when p a b c) == (unless (not p) a b c)

As a matter of style, when is normally used to conditionally produce some side effects, and the value of the when form is normally not used. If the value is relevant, then it may be stylistically more appropriate to use and or if.


[Macro]
unless test {form}*

(unless test form1 form2 ... ) first evaluates test. If the result is not nil, then the forms are not evaluated, and nil is returned. Otherwise the forms constitute an implicit progn and are evaluated sequentially from left to right, and the value of the last one is returned.

(unless p a b c) == (cond ((not p) a b c)) 
(unless p a b c) == (if p nil (progn a b c)) 
(unless p a b c) == (when (not p) a b c)

As a matter of style, unless is normally used to conditionally produce some side effects, and the value of the unless form is normally not used. If the value is relevant, then it may be stylistically more appropriate to use if.


[Macro]
cond {(test {form}*)}*

A cond form has a number (possibly zero) of clauses, which are lists of forms. Each clause consists of a test followed by zero or more consequents. For example:

(cond (test-1 consequent-1-1 consequent-1-2 ...) 
      (test-2) 
      (test-3 consequent-3-1 ...) 
      ... )

The first clause whose test evaluates to non-nil is selected; all other clauses are ignored, and the consequents of the selected clause are evaluated in order (as an implicit progn).

More specifically, cond processes its clauses in order from left to right. For each clause, the test is evaluated. If the result is nil, cond advances to the next clause. Otherwise, the cdr of the clause is treated as a list of forms, or consequents; these forms are evaluated in order from left to right, as an implicit progn. After evaluating the consequents, cond returns without inspecting any remaining clauses. The cond special form returns the results of evaluating the last of the selected consequents; if there were no consequents in the selected clause, then the single (and necessarily non-null) value of the test is returned. If cond runs out of clauses (every test produced nil, and therefore no clause was selected), the value of the cond form is nil.

If it is desired to select the last clause unconditionally if all others fail, the standard convention is to use t for the test. As a matter of style, it is desirable to write a last clause (t nil) if the value of the cond form is to be used for something. Similarly, it is in questionable taste to let the last clause of a cond be a ``singleton clause''; an explicit t should be provided. (Note moreover that (cond ... (x)) may behave differently from (cond ... (t x)) if x might produce multiple values; the former always returns a single value, whereas the latter returns whatever values x returns. However, as a matter of style it is preferable to obtain this behavior by writing (cond ... (t (values x))), using the values function explicitly to indicate the discarding of any excess values.) For example:

(setq z (cond (a 'foo) (b 'bar)))          ;Possibly confusing 
(setq z (cond (a 'foo) (b 'bar) (t nil)))  ;Better 
(cond (a b) (c d) (e))                     ;Possibly confusing 
(cond (a b) (c d) (t e))                   ;Better 
(cond (a b) (c d) (t (values e)))          ;Better (if one value 
                                           ; needed) 
(cond (a b) (c))                           ;Possibly confusing 
(cond (a b) (t c))                         ;Better 
(if a b c)                                 ;Also better

A Lisp cond form may be compared to a continued if-then-else as found in many algebraic programming languages:

(cond (p ...)                            if p then ... 
      (q ...)            roughly         else if q then ... 
      (r ...)          corresponds       else if r then ... 
      ...                   to           ... 
      (t ...))                           else ...


[Macro]
case keyform {({({key}*) | key} {form}*)}*

case is a conditional that chooses one of its clauses to execute by comparing a value to various constants, which are typically keyword symbols, integers, or characters (but may be any objects). Its form is as follows:

(case keyform 
  (keylist-1 consequent-1-1 consequent-1-2 ...) 
  (keylist-2 consequent-2-1 ...) 
  (keylist-3 consequent-3-1 ...) 
  ...)

Structurally case is much like cond, and it behaves like cond in selecting one clause and then executing all consequents of that clause. However, case differs in the mechanism of clause selection.

The first thing case does is to evaluate the form keyform to produce an object called the key object. Then case considers each of the clauses in turn. If key is in the keylist (that is, is eql to any item in the keylist) of a clause, the consequents of that clause are evaluated as an implicit progn; case returns what was returned by the last consequent (or nil if there are no consequents in that clause). If no clause is satisfied, case returns nil.

The keys in the keylists are not evaluated; literal key values must appear in the keylists. It is an error for the same key to appear in more than one clause; a consequence is that the order of the clauses does not affect the behavior of the case construct.

Instead of a keylist, one may write one of the symbols t and otherwise. A clause with such a symbol always succeeds and must be the last clause (this is an exception to the order-independence of clauses). See also ecase and ccase, each of which provides an implicit otherwise clause to signal an error if no clause is satisfied.

If there is only one key for a clause, then that key may be written in place of a list of that key, provided that no ambiguity results. Such a ``singleton key'' may not be nil (which is confusable with (), a list of no keys), t, otherwise, or a cons.


Compatibility note: The Lisp Machine Lisp caseq construct uses eq for the comparison. In Lisp Machine Lisp caseq therefore works for fixnums but not bignums. The MacLisp caseq construct simply prohibits the use of bignums; indeed, it permits only fixnums and symbols as clause keys. In the interest of hiding the fixnum-bignum distinction, and for general language consistency, case uses eql in Common Lisp.

The Interlisp selectq construct is similar to case.



[Macro]
typecase keyform {(type {form}*)}*

typecase is a conditional that chooses one of its clauses by examining the type of an object. Its form is as follows:

(typecase keyform 
  (type-1 consequent-1-1 consequent-1-2 ...) 
  (type-2 consequent-2-1 ...) 
  (type-3 consequent-3-1 ...) 
  ...)

Structurally typecase is much like cond or case, and it behaves like them in selecting one clause and then executing all consequents of that clause. It differs in the mechanism of clause selection.

The first thing typecase does is to evaluate the form keyform to produce an object called the key object. Then typecase considers each of the clauses in turn. The type that appears in each clause is a type specifier; it is not evaluated but is a literal type specifier. The first clause for which the key is of that clause's specified type is selected, the consequents of this clause are evaluated as an implicit progn, and typecase returns what was returned by the last consequent (or nil if there are no consequents in that clause). If no clause is satisfied, typecase returns nil.

As for case, the symbol t or otherwise may be written for type to indicate that the clause should always be selected. See also etypecase and ctypecase, each of which provides an implicit otherwise clause to signal an error if no clause is satisfied.

It is permissible for more than one clause to specify a given type, particularly if one is a subtype of another; the earliest applicable clause is chosen. Thus for typecase, unlike case, the order of the clauses may affect the behavior of the construct. For example:

(typecase an-object 
   (string ...)            ;This clause handles strings 
   ((array t) ...)         ;This clause handles general arrays 
   ((array bit) ...)       ;This clause handles bit arrays 
   (array ...)             ;This handles all other arrays 
   ((or list number) ...)  ;This handles lists and numbers 
   (t ...))                ;This handles all other objects

A Common Lisp compiler may choose to issue a warning if a clause cannot be selected because it is completely shadowed by earlier clauses.



next up previous contents index
Next: Blocks and Exits Up: Control Structure Previous: Establishing New Variable


AI.Repository@cs.cmu.edu