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 |
|---|---|---|
|
|
Yes |
tests a list variable for emptiness |
|
|
No |
tests a list variable for falseness |
|
|
Yes |
tests a list variable for non-emptiness |
|
|
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.
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)))))
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))))
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 .
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.
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:
compress.lisp has already been loaded, it
won't be loaded again
require generates
the appropriate extension automatically.
The disadvantages of require are
require was
temporarily dropped from Common Lisp, so some implementations
don't have it
loop, it has a religious aspect; the battle
is between:
defsystem, and
Unfortunately for the centralists, there is no Common Lisp
standard for defsystem. Unfortunately for the
decentralists, most implementations of require are
pretty weak.
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.