11.2. Lisp Primitive Functions


Now that you've seen how to write a working command, we'll discuss Lisp's primitive functions. These are the building blocks from which you'll build your functions. As mentioned above, Lisp uses functions where other languages would use operators, that is, for arithmetic, comparison, and logic. Table 11-3 shows some Lisp primitive functions that are equivalent to these operators.

Table 11-3. Lisp primitive functions

Arithmetic

+, -, *, /

 

% (remainder)

 

1+ (increment)

 

1- (decrement)

 

max, min

Comparison

>, <, >=, <=

 

/= (not equal)

 

= (for numbers and characters)

 

equal (for strings and other complex objects)

Logic

and, or, not


All the arithmetic functions except 1+, 1-, and % can take arbitrarily many arguments, as can and and or. An arithmetic function returns floating point values only if at least one argument is a floating point number, so for example, (/ 7.0 4) returns 1.75, and (/ 7 4) returns 1. Notice that integer division truncates the remainder.

It may seem inefficient or syntactically ugly to use functions for everything. However, one of the main merits of Lisp is that the core of the language is small and easy to interpret efficiently. In addition, the syntax is not as much of a problem if you have support tools such as Emacs's Lisp modes to help you.

11.2.1 Statement Blocks

We have seen that a statement block can be defined using the let function. We also saw that while and save-excursion include statement blocks. Other important constructs also define statement blocks: progn and other forms of let.

progn, the most basic, has the form:

   (progn      statement-block)

progn is a simple way of making a block of statements look like a single one, somewhat like the curly braces of Java or the begin and end of Pascal. The value returned by progn is the value returned by the last statement in the block. progn is especially useful with control structures like if (see the following discussion) that, unlike while, do not include statement blocks.

The let function has other forms as well. The simplest is:

   (let (var1 var2 ...)      statement-block)

In this case, instead of a list of (var value) pairs, there is simply a list of variable names. As with the other form of let, these become local variables accessible in the statement block. However, instead of initializing them to given values, they are all just initialized to nil. You can actually mix both forms within the same let statement, for example:

   (let (var1 (var2 value2) var3 ...)      statement-block)

In the form of let we saw first, the initial values for the local variables can be function calls (remember that all functions return values). All such functions are evaluated before any values are assigned to variables. However, there may be cases in which you want the values of some local variables to be available for computing the values of others. This is where let*, the final version of let, comes in. let* steps through its assignments in order, assigning each local variable a value before moving on to the next.

For example, let's say we want to write a function goto-percent that allows you to go to a place in the current buffer expressed as a percentage of the text in the buffer. Here is one way to write this function:

(defun goto-percent (pct)   (interactive "nGoto percent: ")   (let* ((size (point-max))         (charpos (/ (* size pct) 100)))     (goto-char charpos)))

As we saw earlier, the interactive function is used to prompt users for values of arguments. In this case, it prompts for the integer value of the argument pct. Then the let* function initializes size to the size of the buffer in characters, then uses that value to compute the character position charpos that is pct (percent) of the buffer's size. Finally, the call of goto-char causes point to be moved to that character position in the current window.

The important thing to notice is that if we had used let instead of let*, the value of size would not be available when computing the value of charpos. let* can also be used in the (var1 var2 ...) format, just like let, but there wouldn't be any point in doing so.

We should also note that a more efficient way to write goto-percent is this:

(defun goto-percent (pct)   (interactive "nPercent: ")   (goto-char (/ (* pct (point-max)) 100)))

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     (condition1     statement-block1)     (condition2     statement-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.[5]

[5] Statement blocks are actually optional; some programmers like to omit the final statement block, leaving the final "condition" as an "otherwise" clause to be executed if all of the preceding conditions evaluate to nil. If the statement block is omitted, the value returned by cond is simply the value of the condition.

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."



Learning GNU Emacs
Learning GNU Emacs, Third Edition
ISBN: 0596006489
EAN: 2147483647
Year: 2003
Pages: 161

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net