our-copy-list is a very important function to understand.
The recursive pattern it uses is the basis for most recursive
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
This hides bad inputs. For example,
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
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:
- A CDR-recursive function that loops down the elements of a list
should reject a non-list input. Examples of such
- A CAR-CDR recursive function that operates on all elements
of a list, no matter how nested, should accept and process a
non-list input. Examples of such functions are
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.
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
recursive-compress are typical names that programmers
generate when at a loss for how to name a function. They're all
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-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
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
Or we could use generated lists.
Note Graham's comment in the text that
list-of is unnecessary.
Common Lisp already has a
make-list that does the same thing. To make a list
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
list-of in the Lisp exercises.
In this class, we'll use
require rather than
load, so you would load the list compression code with
The advantages of
compress.lisphas already been loaded, it won't be loaded again
- if your Lisp uses some extension other than "lisp", you don't
have to change anything, because
requiregenerates the appropriate extension automatically.
The disadvantages of
- it was underspecified in the Common Lisp reference manual, so different implementations do different things
- because of these differences,
requirewas temporarily dropped from Common Lisp, so some implementations don't have it
loop, it has a religious aspect; the battle is between:
- the centralists, who favor defining all module dependencies
in one place with
- the decentralists, who favor defining what a module depends on in the module
- the centralists, who favor defining all module dependencies in one place with
Unfortunately for the centralists, there is no Common Lisp
defsystem. Unfortunately for the
decentralists, most implementations of
A very commonly used non-standard centralist package is
Facility), modeled after
For this class, we use
require to indicate in each
file what other files it needs, and
in cs325.lisp, to
load such files, getting them from the course code site, if not
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))))