[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