: Learning GNU Emacs, 3rd Edition

11.2.2 Control Structures

11.2.2 Control Structures

We already saw that the while function acts as a control structure like similar statements in other languages. There are two other important control structures in Lisp: if and cond.

The if function has the form:

(if condition true-case false-block)

Here, the condition is evaluated; if it is non-nil, true-case is evaluated; if nil, false-block is evaluated. Note that true-case is a single statement whereas false-block is a statement block; false-block is optional.

As an example, let's suppose we're writing a function that performs some complicated series of edits to a buffer and then reports how many changes it made. We're perfectionists, so we want the status report to be properly pluralized, that is to say "made 53 changes" or "made 1 change." This is a common enough programming need that we decide to write a general-purpose function to do it so that we can use it in other projects too.

The function takes two arguments: the word to be pluralized (if necessary) and the count to be displayed (which determines whether it's necessary).

(defun pluralize (word count)
(if (= count 1)
word
(concat word "s")))

The condition in the if clause tests to see if count is equal to 1. If so, the first statement gets executed. Remember that the "true" part of the if function is only one statement, so progn would be necessary to make a statement block if we wanted to do more than one thing. In this case, we have the opposite extreme; our "true" part is a single variable, word. Although this looks strange, it is actually a very common Lisp idiom and worth getting used to. When the condition block is true, the value of word is evaluated, and this value becomes the value of the entire if statement. Because that's the last statement in our function, it is the value returned by pluralize. Note that this is exactly the result we want when count is 1: the value of word is returned unchanged.

The remaining portion of the if statement is evaluated when the condition is false, which is to say, when count has a value other than 1. This results in a call to the built-in concat function, which concatenates all its arguments into a single string. In this case it adds an "s" at the end of the word we've passed in. Again, the result of this concatenation becomes the result of the if statement and the result of our pluralize function.

If you type it in and try it out, you'll see results like this:

(pluralize "goat" 5)
"goats"
(pluralize "change" 1)
"change"

Of course, this function can be tripped up easily enough. You may have tried something like this already:

(pluralize "mouse" 5)
"mouses"

To fix this, we'd need to be able to tell the function to use an alternate plural form for tricky words. But it would be nice if the simple cases could remain as simple as they are now. This is a good opportunity to use an optional parameter. If necessary, we supply the plural form to use; if we don't supply one, the function acts as it did in its first incarnation. Here's how we'd achieve that:

(defun pluralize (word count &optional plural)
(if (= count 1)
word
(if (null plural)
(concat word "s")
plural)))

The "else" part of our code has become another if statement. It uses the null function to check whether we were given the plural parameter or not. If plural was omitted, it has the value nil and the null function returns t if its argument is nil. So this logic reads "if b was missing, just add an s to word; otherwise return the special plural value we were given."

This gives us results like this:

(pluralize "mouse" 5)
"mouses"
(pluralize "mouse" 5 "mice")
"mice"
(pluralize "mouse" 1 "mice")
"mouse"

A more general conditional control structure is the cond function, which has the following form:

(cond
(condition1statement-block1)
(condition2statement-block2)
...)

Java and Perl programmers can think of this as a sequence of if then else if then else if . . . , or as a kind of generalized switch statement. The conditions are evaluated in order, and when one of them evaluates to non-nil, the corresponding statement block is executed; the cond function terminates and returns the last value in that statement block.[77]

We can use cond to give a more folksy feel to our hypothetical status reporter now that it's pluralizing nicely. Instead of reporting an actual numeric value for the number of changes, we could have it say no, one, two, or many as appropriate. Again we'll write a general function to do this:

(defun how-many (count)
(cond ((zerop count) "no")
((= count 1) "one")
((= count 2) "two")
(t "many")))

The first conditional expression introduces a new primitive Lisp function, zerop. It checks whether its argument is zero, and returns t (true) when it is. So when count is zero, the cond statement takes this first branch, and our function returns the value no. This strange function name bears a little explanation. It is pronounced "zero-pee" and is short for "zero predicate." In the realm of mathematical logic from which Lisp evolved, a predicate is a function that returns true or false based on some attribute of its argument. Lisp has a wide variety of similar predicate functions, with structurally related names. When you run into the next one, you'll understand it. (Of course, you might now expect the null function we introduced in the previous example to be called "nilp" instead. Nobody's perfectly consistent.)

The next two conditional expressions in the cond statement check if count is 1 or 2 and cause it to return "one" or "two" as appropriate. We could have written the first one using the same structure, but then we'd have missed out on an opportunity for a digression into Lisp trivia!

The last conditional expression is simply the atom t (true), which means its body is executed whenever all the preceding expressions failed. It returns the value many. Executing this function gives us results like these:

(how-many 1)
"one"
(how-many 0)
"no"
(how-many 3)
"many"

Combining these two helper functions into a mechanism to report the change count for our fancy command is easy.

(defun report-change-count (count)
(message "Made %s %s." (how-many count) (pluralize "change" count)))

We get results like these:

(report-change-count 0)
"Made no changes."
(report-change-count 1)
"Made one change."
(report-change-count 1329)
"Made many changes."


: 0.216. /Cache: 3 / 0