• Aucun résultat trouvé

Define a function to take a square array (an array whose dimensions are (n n)) and rotate it 90° clockwise:

Dans le document ANSI Common Lisp (Page 96-104)

Specialized Data Structures

SHAPE = SPHERICAL SIZE = GIANT

1. Define a function to take a square array (an array whose dimensions are (n n)) and rotate it 90° clockwise:

> (quarter-turn #2A((a b) (c d)))

#2A((C A) (D B))

You'll need array-dimensions (page 361).

2. Read the description of reduce on page 368, then use it to define:

(a) c o p y - l i s t (b) r e v e r s e (for lists)

3. Define a structure to represent a tree where each node contains some data and has up to three children. Define

(a) a function to copy such a tree (so that no node in the copy is eql to a node in the original)

(b) a function that takes an object and such a tree, and returns true if the object is e q l to the data field of one of the nodes

4. Define a function that takes a BST and returns a list of its elements ordered from greatest to least.

5. Define b s t - a d j oin. This function should take the same arguments as b s t - i n s e r t , but should only insert the object if there is nothing eql to it in the tree.

6. The contents of any hash table can be described by an assoc-list whose elements are (k . v), for each key-value pair in the hash table. Define a function that

(a) takes an assoc-list and returns a corresponding hash table (b) takes a hash table and returns a corresponding assoc-list

5

Control

Section 2.2 introduced the Common Lisp evaluation rule, which by now should be familiar from long experience. What the operators in this chapter have in common is that they all violate the evaluation rule. They let you direct the course that evaluation will take through the text of a program. If ordinary function calls are the leaves of a Lisp program, these operators are used to build the branches.

5.1 Blocks

Common Lisp has three basic operators for creating blocks Of code: progn, block, and tagbody. We have seen progn already. The expressions within its body are evaluated in order, and the value of the last is returned:0

> (progn

(format t "a") (format t "b") (+ 11 12)) ab

23

Since only the value of the last expression is returned, the use of progn (or any block) implies side-effects.

A block is like a progn with a name and an emergency exit. The first argument should be a symbol. This becomes the name of the block. At any point within the body, you can halt evaluation and return a value immediately by using r e t u r n - f rom with the block's name:

81

> (block head

(format t "Here we go.") (return-from head 'idea)

(format t "We'll never see t h i s . " ) ) Here we go.

IDEA

Calling return-from allows your code to make a sudden but graceful exit from anywhere in a body of code. The second argument to return-from is returned as the value of the block named by the first. Expressions after the return-from are not evaluated.

There is also a return macro, which returns its argument as the value of an enclosing block named n i l :

> (block n i l (return 27)) 27

Many Common Lisp operators that take a body of expressions implicitly enclose the body in a block named n i l . All iteration constructs do, for example:

> (dolist (x ' (a b c d e)) (format t "~A " x) (if (eql x >c)

(return 'done))) A B C

DONE

The body of a function defined with def un is implicitly enclosed in a block with the same name as the function, so you can say:

(defun foo ()

(return-from foo 27))

Outside of an explicit or implicit block, neither return-from nor return will work.

Using return-from we can write a better version of read-integer:

(defun read-integer (str) (let ((accum 0))

(dotimes (pos (length str))

(let ((i (digit-char-p (char str pos)))) (if i

(setf accum (+ (* accum 10) i)) (return-from read-integer nil)))) accum))

5.2 CONTEXT 83 The version on page 68 had to check all the characters before building the integer. Now the two steps can be combined, because we can abandon the calculation if we encounter a character that's not a digit.

The third basic block construct is tagbody, within which you can use gotos. Atoms appearing in the body are interpreted as labels; giving such a label to go sends control to the expression following it. Here is an exceedingly ugly piece of code for printing out the numbers from 1 to 10:

> (tagbody (setf x 0) top

(setf x (+ x 1)) (format t "~A " x) (if (< x 10) (go top))) 1 2 3 4 5 6 7 8 9 10 NIL

This operator is mainly something that other operators are built upon, not something you would use yourself. Most iteration operators have an implicit tagbody, so it's possible (though rarely desirable) to use labels and go within their bodies.

How do you decide which block construct to use? Nearly all the time you'll use progn. If you want to allow for sudden exits, use block instead.

Most programmers will never use tagbody explicitly.

5.2 Context

Another operator we've used to group expressions is l e t . It takes a body of code, but also allows us to establish new variables for use within the body:

> ( l e t ((x 7) (y 2))

(format t "Number") (+ x y))

Number 9

An operator like l e t creates a new lexical context. Within this context there are two new variables, and variables from outer contexts may have thereby become invisible.

Conceptually, a l e t expression is like a function call. Section 2.14 showed that, as well as referring to a function by name, we could refer to it literally by using a lambda expression. Since a lambda expression is like the

name of a function, we can use one, as we would a function name, as the first element in a function call:

> ((lambda (x) (+ x 1)) 3) 4

The preceding l e t expression is exactly equivalent to:

((lambda (x y)

(format t "Number") (+ x y))

7 2)

Any questions you have about l e t should be dealt with by passing the buck to lambda, because entering a l e t is conceptually equivalent to doing a function call.0

One of the things this model makes clear is that the value of one l e t -created variable can't depend on other variables -created by the same l e t . For example, if we tried to say

( l e t ((x 2)

(y (+ x 1 ) ) ) (+ x y))

then the x in (+ x 1) would not be the x established in the previous line, because the whole expression is equivalent to

((lambda (x y) (+ x y)) 2

(+ x 1))

Here it's obvious that the (+ x 1) passed as an argument to the function cannot refer to the parameter x within the function.

So what if you do want the value of one new variable to depend on the value of another variable established by the same expression? In that case you would use a variant called l e t * :

> ( l e t * ((x 1)

(y (+ x 1 ) ) ) (+ x y ) )

3

A l e t * is functionally equivalent to a series of nested l e t s . This particular example is equivalent to:

5.3 CONDITIONALS 85 ( l e t ((x 1))

( l e t ((y (+ x 1 ) ) ) (+ x y ) ) )

In both l e t and l e t * , initial values default to n i l . Such variables need not be enclosed within lists:

> ( l e t (x y) ( l i s t x y)) (NIL NIL)

The d e s t r u c t u r i n g - b i n d macro is a generalization of l e t . Instead of single variables, it takes a pattern—one or more variables arranged in the form of a tree—and binds them to the corresponding parts of some actual tree. For example:

> ( d e s t r u c t u r i n g - b i n d (w (x y) . z) ' ( a (b c) d e) ( l i s t w x y z ) )

(A B C (D E))

It causes an error if the tree given as the second argument doesn't match the pattern given as the first.

5.3 Conditionals

The simplest conditional is if; all the others are built upon it. The simplest after if is when, which takes an expression and a body of code. The body will be evaluated if the test expression returns true. So

(when (oddp t h a t )

(format t "Hmm, t h a t ' s odd.") (+ t h a t 1))

is equivalent to (if (oddp t h a t )

(progn

(format t "Hmm, t h a t ' s odd.") (+ t h a t 1 ) ) )

The opposite of when is u n l e s s ; it takes the same arguments, but the body will be evaluated only if the test expression returns false.

The mother of all conditionals (in both senses) is cond, which brings two new advantages: it allows multiple conditions, and the code associated with each has an implicit progn. It's intended for use in situations where we would otherwise have to make the third argument of an i f another if. For example, this pseudo-member

(defun our-member (obj 1st) (if (atom 1st)

nil

(if (eql (car 1st) obj) 1st

(our-member obj (cdr 1st))))) could also be defined as

(defun our-member (obj 1st) (cond ((atom 1st) nil)

((eql (car 1st) obj) 1st)

(t (our-member obj (cdr 1st)))))

In fact, a Common Lisp implementation will probably implement cond by translating the latter into the former.

In general, cond takes zero or more arguments. Each one must be a list consisting of a condition followed by zero or more expressions. When the cond expression is evaluated, the conditions are evaluated in order until one of them returns true. When it does, the expressions associated with it are evaluated in order, and the value of the last is returned as the value of the cond. If there are no expressions after the successful condition

> (cond (99)) 99

the value of the condition itself is returned.

Since a cond clause with a condition of t will always succeed, it is conventional to make the final, default clause have t as the condition. If no clause succeeds, the cond returns n i l , but it is usually bad style to take advantage of this return value. (For an example of the kind of problem that can occur, see page 292.)

When you want to compare a value against a series of constants, there is case. We might use case to define a function to return the number of days in a month:

(defun month-length (mon) (case mon

((jan mar may j u l aug oct dec) 31) ((apr jun sept nov) 30)

(feb ( i f (leap-year) 29 28)) (otherwise "unknown month")))

A case expression begins with an argument whose value will be compared against the keys in each clause. Then come zero or more clauses, each one

5.4 ITERATION 87

beginning with either a key, or a list of keys, followed by zero or more expressions. The keys are treated as constants; they will not be evaluated.

The value of the first argument is compared (using eql) to the key/s at the head of each clause. If there is a match, the expressions in the rest of that clause are evaluated, and the value of the last is returned as the value of the case.

The default clause may have the key t or otherwise. If no clause succeeds, or the successful clause contains only keys,

> (case 99 (99)) NIL

then the case returns n i l .

The typecase macro is similar to case, except that the keys in each clause should be type specifiers, and the value of the first argument is compared to the keys using typep instead of eql. (An example of typecase appears on page 107.)

5.4 Iteration

The basic iteration operator is do, which was introduced in Section 2.13.

Since do contains both an implicit block and an implicit tagbody, we now know that it's possible to use return, return-f rom, and go within the body of a do.

Section 2.13 mentioned that the first argument to do had to be a list of

Dans le document ANSI Common Lisp (Page 96-104)