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:
- Length and lists
- Integer division
- Using
ceiling
,floor
,round
, andtruncate
- Declared numerics
- Generating symbols
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 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-2-p list) (list-length>1 list) (list-length>2 list) (list-length> n 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 declared numeric operator for division.single-float
- Using
ceiling
,floor
,round
, andtruncate
First of all, remember that
ceiling
,floor
,round
, andtruncate
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 arefixnum
values:(round& a b)
or, if a and b can be floating-point values:(round$ (float a) (float b))
Second, select theceiling
,floor
,round
, andtruncate
function that is the most appropriate and specific for the situation. For example, usetruncate
when its argument values will always be positive rather thanfloor
. - 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 useintern
without an explicit package specifier (unless you are really, really, certain what the current package will be whenintern
is executed—and then still use an explicit specifier!). The sole exception to specifying the package withintern
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 being*print-case*
: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 to*print-case*
: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 bothobject-name
andslot-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)))