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.
|