Coding Advice

Common Lisp and GBBopen offer substantial flexibility when it comes to coding style and coding approaches. We've seen code examples that are inefficient, fragile, and sometimes errors waiting to happen. We present some of these below:

Code examples

  • Length and lists

    Determining the entire length of a list when it is not needed is inefficient. Here are some typical examples:

      (zerop (length list))
      (= (length list) 1)
      (>= (length list) 1)
      
    Here are a better versions:
      (null list)
      (and (consp list) (null (cdr list)))
      (and (consp list) (consp (cdr list)))
      
    GBBopen Tools provides predicates that makes these tests even clearer:
      (null list)
      (list-length-1-p list)
      (list-length>1-p list)
      

  • Integer division

    Can I have your undivided attention? Never use division with integer values unless you really want the possibility of creating a ratio. Unbounded-precision arithmetic is a wonderful Common Lisp capability, but most of the time it is not what is intended. This is a particularly inefficient example:

      (float (/ a b))
      
    Here is a better version:
      (/$ (float a) (float b))
      
    where /$ is GBBopen Tool's single-float declared numeric operator for division.

  • Using ceiling, floor, round, and truncate

    First of all, remember that ceiling, floor, round, and truncate can be binary functions. An inefficient example:

      (round (/ a b))
      
    Here is an improved version:
      (round a b)
      
    Note that the second value (the remainder) will be different in the binary version (it will be b times the remainder value that is returned in the inefficient example). Here is an even better version, using declared numerics, when a and b are fixnum values:
      (round& a b)
      
    or, if a and b can be floating-point values:
      (round$ (float a) (float b))
      
    Second, select the ceiling, floor, round, and truncate function that is the most appropriate and specific for the situation. For example, use truncate when its argument values will always be positive rather than floor.

  • Use declared numerics

    Oh, have we mentioned using GBBopen Tool's convenient declared numeric operators enough yet?

  • Generating symbols

    The following is all too prevalent:

      (intern (format nil "~a-~a" object-name slot-name))
      
    First, don't use intern without an explicit package specifier (unless you are really, really, certain what the current package will be when intern is executed—and then still use an explicit specifier!). The sole exception to specifying the package with intern is in a definitional situation where the user is assumed to be using the defining form in the desired package.

    Second, avoid the (format nil ) idiom; it depends on the value of *print-case* being :upcase when using a Common Lisp in standard (ANSI) case mode. Many of us prefer not to have Common Lisp “shout” at us and set *print-case* to :downcase, breaking the above example.

    Here is a much better version:

      (intern (concatenate 'string
                 (symbol-name object-name) "-" (symbol-name slot-name))
               (load-time-value (find-package ':my-package)))
      
    assuming that both object-name and slot-name are symbols. This version is faster computationally on most Common Lisps, allocates less, is insensitive to the value of *print-case*, and handles so-called “modern” case mode. If the additional typing bothers you, define a concatenating symbol-generator function to hide the details!

    Here's another example of symbol-building poor practice:

      (intern (format nil "MY-MANIPULATOR-OF-~a" symbol))
      

    And the better version:

      (intern (concatenate 'string
                 (load-time-value (symbol-name '#:MY-MANIPULATOR-OF-))
                 (symbol-name symbol))
               (load-time-value (find-package ':my-package)))
      
If you have some coding advice to share, please send a note to comments@GBBopen.org.

Last updated: April 30, 2010