The Conditionals: if and Beyond

Now that you understand how Lisp handles true and false, let’s look at if and some of the other conditional commands.

One Thing at a Time with if

The if command can be used to make different things happen when things are true (such as when 1 + 2 = 3) or false (such as when 1 + 2 = 4).

> (if (= (+ 1 2) 3)
      'yup
      'nope)

YUP

> (if (= (+ 1 2) 4)
      'yup
      'nope)

NOPE

The if command can also be used to check whether a list is empty:

> (if '(1)
      'the-list-has-stuff-in-it
      'the-list-is-empty)

THE-LIST-HAS-STUFF-IN-IT

> (if '()
      'the-list-has-stuff-in-it
      'the-list-is-empty)

THE-LIST-IS-EMPTY

So far, the only way to branch on a condition that we’ve looked at has been the if command:

> (if (oddp 5)
      'odd-number
      'even-number)

ODD-NUMBER

All we’re doing here is checking whether the number 5 is odd, then, depending on the result, evaluating one of the two following expressions in the if form. Since 5 is odd, it evaluates the first such expression, and the form as a whole returns odd-number.

There’s a lot happening in this harmless-looking little command—stuff that’s important to understanding Lisp. Here are two important observations:

  • Only one of the expressions after the if is actually evaluated.

  • We can only do one thing in an if statement.

Usually, when a function is executed in Lisp, all the expressions after the function name are evaluated, before the function itself is evaluated. However, if does not follow these rules. To see this, consider the following example:

> (if (oddp 5)
    'odd-number
    (/ 1 0))

ODD-NUMBER

Any self-respecting, law-abiding Lisp function would kick your butt to the curb if you tried to run this code, because you’re dividing by zero.

But if is not just a function. It’s a special form, which gives it special privileges, such as the right to not evaluate all its parameters in the normal way. This makes sense, since the whole point of a condition is to run some stuff but not other stuff. In this case, it just merrily ignores the division by zero, since it’s in the part of the branch that applies only to even numbers. Conditional commands in Lisp are typically special forms.

image with no caption

Note

Some of the conditional commands may be macros, which are something like user-created special forms. Being a special form usually implies that a command is directly “baked in” to the language. In Chapter 16, you’ll learn how to write such macros yourself.

Since only one expression inside an if is ever evaluated, it’s impossible to do two or more separate things inside your branch.

There is actually a clever style of programming (called functional programming, as we’ll discuss in Chapter 14), which considers this a Good Thing. However, for cases when you really want to do more than one thing, you can use a special command, progn, to wedge in extra commands in a single expression. With progn, only the last evaluation is returned as the value of the full expression. In this next example, for instance, we use the command to set a special global variable directly inside our conditional branch.

> (defvar *number-was-odd* nil)

> (if (oddp 5)
      (progn (setf *number-was-odd* t)
             'odd-number)
      'even-number)

ODD-NUMBER

> *number-was-odd*

T
image with no caption

Going Beyond if: The when and unless Alternatives

Since it’s kind of a pain to use progn every time you want to do multiple things inside an if, Lisp has several other commands that include an implicit progn. The most basic of these are when and unless:

> (defvar *number-is-odd* nil)
> (when (oddp 5)
        (setf *number-is-odd* t)
        'odd-number)

ODD-NUMBER

> *number-is-odd*

T

> (unless (oddp 4)
          (setf *number-is-odd* nil)
          'even-number)

EVEN-NUMBER

> *number-is-odd*

NIL

With when, all the enclosed expressions are evaluated when the condition is true. With unless, all the enclosed expressions are evaluated when the condition is false. The trade-off is that these commands can’t do anything when the condition evaluates in the opposite way; they just return nil and do nothing.

The Command That Does It All: cond

But what do you do if you’re the kind of coder who wants it all? Maybe you just ain’t in a compromisin’ mood and want a function that will do everything! Well, Lisp has you covered.

image with no caption

The cond form is the classic way to do branching in Lisp. Through the liberal use of parentheses, it allows for an implicit progn, can handle more than one branch, and can even evaluate several conditions in succession. Since cond has been around since the Lisp Stone Age, and it’s comprehensive in its abilities, many Lisp programmers consider it to be the one true Lisp conditional.

Here’s an example:

> (defvar *arch-enemy* nil)
 > (defun pudding-eater (person)
        (cond ((eq person 'henry) (setf *arch-enemy* 'stupid-lisp-alien)
                                   '(curse you lisp alien - you ate my pudding))
               ((eq person 'johnny) (setf *arch-enemy* 'useless-old-johnny)
                                   '(i hope you choked on my pudding johnny))
               (t                  '(why you eat my pudding stranger ?))))


  > (pudding-eater 'johnny)
  (I HOPE YOU CHOKED ON MY PUDDING JOHNNY)
  > *arch-enemy*
  JOHNNY
  > (pudding-eater 'george-clooney)
  (WHY YOU EAT MY PUDDING STRANGER ?)
image with no caption

As you can see, the body of a cond uses a layer of parentheses to separate the different branches of the condition. Then the first expression of each parenthesized part contains the condition for making that branch active. In our example, we have different branches for each type of pudding thief: one for Henry , one for Johnny , and one for everyone else . We use eq to compare the supplied person’s name with each potential perpetrator.

The conditions in a cond form are always checked from the top down, so the first successful match drives the behavior. In this example, the last branch has a condition of t (for true), guaranteeing that at least the last branch will always be evaluated. This is a common cond idiom.

As with when and unless, the triggered branch may contain more than one command, since there is an implicit progn. In this case, the first two branches set an extra *arch-enemy* variable, besides supplying a return variable.

Branching with case

Let’s look at one final Lisp command: the case form. It is common to use the eq function for conditionals, and case lets you supply a value to compare against. Using case, we can rewrite the previous example as follows:

> (defun pudding-eater (person)
         (case person
              ((henry)   (setf *arch-enemy* 'stupid-lisp-alien)
                         '(curse you lisp alien - you ate my pudding))
              ((johnny)  (setf *arch-enemy* 'useless-old-johnny)
                         '(i hope you choked on my pudding johnny))
              (otherwise '(why you eat my pudding stranger ?))))

This version of the code is a lot easier on the eyes. The name of the person handled by each part of the case statement is clearly visible—it’s not hidden inside an equality check. Depending on which version of Lisp you use, a case statement like this may also be more efficient, especially with longer statements, where larger numbers of cases are handled.

Warning

Because the case command uses eq for comparisons, it is usually used only for branching on symbol values. It cannot be used to branch on string values, among other things. See Comparing Stuff: eq, equal, and More in Comparing Stuff: eq, equal, and More for details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.235.8