[3-11] Miscellaneous things to consider when debugging code.
This question lists a variety of problems to watch out for when
debugging code. This is sort of a catch-all question for problems too
small to merit a question of their own. See also question [1-3] for
some other common problems.
Functions:
* (flet ((f ...)) (eq #'f #'f)) can return false.
* The function LIST-LENGTH is not a faster, list-specific version
of the sequence function LENGTH. It is list-specific, but it's
slower than LENGTH because it can handle circular lists.
* Don't confuse the use of LISTP and CONSP. CONSP tests for the
presence of a cons cell, but will return NIL when called on NIL.
LISTP could be defined as (defun listp (x) (or (null x) (consp x))).
* Use the right test for equality:
EQ tests if the objects are identical -- numbers with the
same value need not be EQ, nor are two similar lists
necessarily EQ. Similarly for characters and strings.
For instance, (let ((x 1)) (eq x x)) is not guaranteed
to return T.
EQL Like EQ, but is also true if the arguments are numbers
of the same type with the same value or character objects
representing the same character. (eql -0.0 0.0) is not
guaranteed to return T.
EQUAL Tests if the arguments are structurally isomorphic, using
EQUAL to compare components that are conses, bit-vectors,
strings or pathnames, and EQ for all other data objects
(except for numbers and characters, which are compared
using EQL). Except for strings and bit-vectors, arrays
are EQUAL only if they are EQ.
EQUALP Like EQUAL, but ignores type differences when comparing
numbers and case differences when comparing characters.
= Compares the values of two numbers even if they are of
different types.
CHAR= Case-sensitive comparison of characters.
CHAR-EQUAL Case-insensitive comparison of characters.
STRING= Compares two strings, checking if they are identical.
It is case sensitive.
STRING-EQUAL Like STRING=, but case-insensitive.
* Some destructive functions that you think would modify CDRs might
modify CARs instead. (E.g., NREVERSE.)
* READ-FROM-STRING has some optional arguments before the
keyword parameters. If you want to supply some keyword
arguments, you have to give all of the optional ones too.
* If you use the function READ-FROM-STRING, you should probably bind
*READ-EVAL* to NIL. Otherwise an unscrupulous user could cause a
lot of damage by entering
#.(shell "cd; rm -R *")
at a prompt.
* Only functional objects can be funcalled in CLtL2, so a lambda
expression '(lambda (..) ..) is no longer suitable. Use
#'(lambda (..) ..) instead. If you must use '(lambda (..) ..),
coerce it to type FUNCTION first using COERCE.
Methods:
* PRINT-OBJECT methods can make good code look buggy. If there is a
problem with the PRINT-OBJECT methods for one of your classes, it
could make it seem as though there were a problem with the object.
It can be very annoying to go chasing through your code looking for
the cause of the wrong value, when the culprit is just a bad
PRINT-OBJECT method.
Initialization:
* Don't count on array elements being initialized to NIL, if you don't
specify an :initial-element argument to MAKE-ARRAY. For example,
(make-array 10) => #(0 0 0 0 0 0 0 0 0 0)
Iteration vs closures:
* DO and DO* update the iteration variables by assignment; DOLIST and
DOTIMES are allowed to use assignment (rather than a new binding).
(All CLtL1 says of DOLIST and DOTIMES is that the variable "is
bound" which has been taken as _not_ implying that there will be
separate bindings for each iteration.)
Consequently, if you make closures over an iteration variable
in separate iterations they may nonetheless be closures over
the same variable and hence will all refer to the same value
-- whatever value the variable was given last. For example,
(let ((fns '()))
(do ((x '(1 2) (cdr x)))
((null x))
(push #'(lambda () x)
fns))
(mapcar #'funcall (reverse fns)))
returns (nil nil), not (1 2), not even (2 2). Thus
(let ((l nil))
(dolist (a '(1 2 3) l)
(push #'(lambda () a)
l)))
returns a list of three closures closed over the same bindings, whereas
(mapcar #'(lambda (a) #'(lambda () a)) '(1 2 3))
returns a list of closures over distinct bindings.
Defining Variables and Constants:
* (defvar var init) assigns to the variable only if it does not
already have a value. So if you edit a DEFVAR in a file and
reload the file only to find that the value has not changed,
this is the reason. (Use DEFPARAMETER if you want the value
to change upon reloading.) DEFVAR is used to declare a variable
that is changed by the program; DEFPARAMETER is used to declare
a variable that is normally constant, but which can be changed
to change the functioning of a program.
* DEFCONSTANT has several potentially unexpected properties:
- Once a name has been declared constant, it cannot be used a
the name of a local variable (lexical or special) or function
parameter. Really. See page 87 of CLtL2.
- A DEFCONSTANT cannot be re-evaluated (eg, by reloading the
file in which it appears) unless the new value is EQL to the
old one. Strictly speaking, even that may not be allowed.
(DEFCONSTANT is "like DEFPARAMETER" and hence does an
assignment, which is not allowed if the name has already
been declared constant by DEFCONSTANT.)
Note that this makes it difficult to use anything other
than numbers, symbols, and characters as constants.
- When compiling (DEFCONSTANT name form) in a file, the form
may be evaluated at compile-time, load-time, or both.
(You might think it would be evaluated at compile-time and
the _value_ used to obtain the object at load-time, but it
doesn't have to work that way.)
Declarations:
* You often have to declare the result type to get the most
efficient arithmetic. Eg,
(the fixnum (+ (the fixnum e1) (the fixnum e2)))
rather than
(+ (the fixnum e1) (the fixnum e2))
* Declaring the iteration variable of a DOTIMES to have type FIXNUM
does not guarantee that fixnum arithmetic will be used. That is,
implementations that use fixnum-specific arithmetic in the presence
of appropriate declaration may not think _this_ declaration is
sufficient. It may help to declare that the limit is also a
fixnum, or you may have to write out the loop as a DO and add
appropriate declarations for each operation involved.
FORMAT related errors:
* When printing messages about files, filenames like foo~ (a GNU-Emacs
backup file) may cause problems with poorly coded FORMAT control
strings.
* Beware of using an ordinary string as the format string,
i.e., (format t string), rather than (format t "~A" string).
* FORMAT returns NIL, so if you added a format statement at the end
of a function for debugging purposes, and that function normally
returns a value to the caller, you may have changed the behavior
of your program.
Miscellaneous:
* Be careful of circular lists and shared list structure.
* Watch out for macro redefinitions.
* A NOTINLINE may be needed if you want SETF of SYMBOL-FUNCTION to
affect calls within a file. (See CLtL2, page 686.)
* When dividing two numbers, beware of creating a rational number where
you intended to get an integer or floating point number. Use TRUNCATE
or ROUND to get an integer and FLOAT to ensure a floating point
number. This is a major source of errors when porting ZetaLisp or C
code to Common Lisp.
* If your code doesn't work because all the symbols are mysteriously
in the keyword package, one of your comments has a colon (:) in
it instead of a semicolon (;).
* If you redefine a function while in the debugger, the redefinition
may not take effect immediately. This will happen, for example,
when the execution stack is halted near the invocation of the function.
The function pointer on the stack will still be pointing to the
old definition. Go up the stack a few levels before restarting to
avoid reusing the old definition.
Go Back Up
Go To Previous
Go To Next