Page 13

The comment that not and null do exactly the same thing is correct but this does not make the two functions interchangable.

Use not on values that are conceptually true or false, i.e., calls to predicate such as atom or some, or variables that hold flag values.

Use null on variables and function calls that contain or return lists. (Actually, in many cases you should use endp.)

Some examples:

Code

Good Style?

Why

(null l)

Yes

tests a list variable for emptiness

(not l)

No

tests a list variable for falseness

(not (null l))

Yes

tests a list variable for non-emptiness

(null (> x 2))

No

tests a predicate for emptiness

Final note: even though (not (null l)) is technically equivalent to just l, use the former not the latter, if l is a variable containing a list, as its name implies.

Page 16

The definition of our-member is a good example of Graham's preference for if over cond. The cond version is more standard Common Lisp, because it avoids the nested conditional:

(defun our-member (obj lst)
  (cond ((null lst) nil)
        ((eql obj (car lst)) lst)
        (t (our-member obj (cdr lst)))))
 

Page 24

show-squares is another example where cond is more appropriate. In this case, it avoids the need for a nested progn.

(defun show-squares (i end)
  (cond ((> i end) 'done)
        (t (format t "~A ~A~%" i (* i i))
           (show-squares (+ i 1) end))))
 

Page 37

compr and n-elts are both terrible function names. The former suggests nothing at all, except a confusing similarity to compress, and the latter suggests that it generates a list of N elements, not a pair of the form (number element).

Names like compr, compress2, recursive-compress are typical names that programmers generate when at a loss for how to name a function. They're all terrible.

Here's an important rule for names:

Never name a function by its relationship to another function. Always name a function by the task it does.

If you follow the Cardinal Rule of Functions, then generating a good name should not be hard. Just name it for the single task that it does.

If two functions do similar tasks, they usually differ by the kind of arguments they take, e.g., compress-list versus compress-string. If they do the same thing to the same arguments, then why are there two functions?

In this particular case, compr's primary task is not "compress a list." It's "given an object and a list, collect a run for the given object." The run collected is returned in the form of a pair of the form (length object).

Unfortunately, compr has a secondary task, which is to recursively repeat this process with the remainder of the list. This is a common problem with recursive code. It mixes control structure with tasks and that makes it harder to name. The best we can do is probably something like scan-and-compress.

Or we could do generators.html .

Page 38

list-of is unnecessary. Common Lisp already has a function make-list that does the same thing.

This is fortunate because there's a much more interesting use for the name list-of in the macro exercises.

Page 39

In this class, we'll use require rather than load, so you would load the list compression code with

(require "compress")
 

The advantages of require over load are:

The disadvantages of require are

Unfortunately for the centralists, there is no Common Lisp standard for defsystem. Unfortunately for the decentralists, most implementations of require are pretty weak.

Page 52

bfs is a terrible name.Also, the nested let's might be better done as a subfunction, e.g.,

(defun breadth-first-search (end queue net)
  (if (null queue)
      nil
      (expand-queue end (car queue) (cdr queue) net)))

(defun expand-queue (end path queue net)
  (let ((node (car path)))
    (if (eql node end)
        (reverse path)
        (breadth-first-search end
                              (append queue
                                      (new-paths path node net))
                              net))))

Comments? Send mail to Chris Riesbeck.