Page 36

our-copy-list is a very important function to understand. The recursive pattern it uses is the basis for most recursive list-transforming functions.

Unfortunately, Graham uses a poor predicate to test for the end of the list. The code should read

(defun our-copy-list (lst)
  (cond ((null lst) lst)
    (t (cons (car lst) (our-copy-list (cdr lst))))))

You could also use endp rather than null but there's no particular reason to do so. endp does an extra (cheap) test to see if it should signal an error on a non-list, but such an error would be raised in this code anyway when the second branch is executed.

Graham's version uses atom instead of null. This hides bad inputs. For example, (our-copy-list 1) returns 1 instead of signalling a non-list was passed in. Hiding bad inputs leads to code that's much harder to debug.

compress on page 37, which uses the predicate consp, is another example of this. The function will return inputs like 12 and A without error.

When should you allow non-lists and when should you signal an error? A good rule of thumb is:

It's not worth doing extra work to check to make sure a list is properly formed, but it's trivial and cheap to use the appropriate predicate to catch most problems.

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:

Name a function by the task it does, not how it does it, nor how it relates to another function.

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

Note Graham's comment in the text that list-of is unnecessary. Common Lisp already has a function make-list that does the same thing. To make a list of three ho's, you'd write:

(make-list 3 :initial-element 'ho)

This uses a keyword argument. See page 44.

There's a much more interesting use for the name list-of in the Lisp 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.

A very commonly used non-standard centralist package is ASDF (Another System Definition Facility), modeled after defsystem.

For this class, we use require to indicate in each file what other files it needs, and include, defined in cs325.lisp, to load such files, getting them from the course code site, if not locally present.

Page 52

bfs is a terrible name, because it says how it works, not what it does. 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.