Common Lisp Tips

Oct 21

:initform and :default-initargs

There are two means of automatic initialization of CLOS slot values, :initform and :default-initargs. Chris Riesbeck makes a strong case for preferring :default-initargs.

Here’s a simplified example of the syntactic difference, taken from some Quicklisp code. Consider modeling version control commands with CLOS. Here’s a possible design:

(defclass vc-command ()
    :initarg :command
    :accessor command)
    :initarg :checkout-subcommand
    :accessor checkout-subcommand)))

To make subclasses for CVS and git, one option is to use the initform slot option:

(defclass vc-git (vc-command)
    :initform "git")
    :initform "clone")))

(defclass vc-cvs (vc-command)
    :initform "cvs")
    :initform "co")))

I prefer to use :default-initargs to avoid recapping the slots:

(defclass vc-git (vc-command)
   :command "git"
   :checkout-subcommand "clone"))

(defclass vc-cvs (vc-command)
   :command "cvs"
   :checkout-subcommand "co"))

:default-initargs also need not correspond to slots. They can be used to pass extra arguments to initialization functions like initialize-instance:

(defclass fribble (dibble)
   :persist nil))

(defmethod initialize-instance :after ((instance dibble) &key persist)
  (when persist
    (persist-object instance)))

Oct 20

Multiple value division

floor, truncate, and related functions return multiple values for division-related operations: the quotient, and the remainder. There are several situations where both values are useful.

For example, if you have a vector of octet values and you want to find the value of a particular bit, you can use something like this:

(defun bit-ref (octet-vector index)
  (multiple-value-bind (octet-index bit-index)
      (truncate index 8)
    (ldb (byte 1 bit-index)
         (aref octet-vector octet-index))))

Another use is copying a known amount of data in fixed-size chunks, e.g. when copying a file somewhere. The quotient represents the number of full chunks occupied by the data, and the remainder is the count of elements in the final partial chunk.

Oct 19

The Common Lisp and Unix epochs

The Common Lisp epoch begins at 00:00 on January 1, 1900, GMT. get-universal-time returns a universal time, defined as the number of seconds elapsed since then.

The Unix epoch begins at 00:00 on January 1, 1970, GMT. time() returns the number of seconds elapsed since then.

It’s easy to get the difference between a Common Lisp universal time and a Unix epoch timestamp:

* (encode-universal-time 0 0 0 1 1 1970 0)

When working with Unix epoch timestamp data, it’s easy to write conversion functions:

(defvar *unix-epoch-difference*
  (encode-universal-time 0 0 0 1 1 1970 0))

(defun universal-to-unix-time (universal-time)
  (- universal-time *unix-epoch-difference*))

(defun unix-to-universal-time (unix-time)
  (+ unix-time *unix-epoch-difference*))

(defun get-unix-time ()
  (universal-to-unix-time (get-universal-time)))

Comparing CL output to the shell:

* (get-unix-time)
$ date +%s

(My CL implementation doesn’t constant-fold the call to encode-universal-time. Bummer.)

Oct 18

"How do I apply AND?"

I sometimes see a question like this: “I have a list of things and I want to see if they’re all true, so how can I apply ‘and’ to it?” Since and is a macro, it can’t be applied, but there are standard functions that can answer the question instead.

(every #’identity list) will return nil as soon as it encounters any nil entries in list, and true if no items are nil. (notany #’null list) is equivalent.

Oct 17

Controlling loop flow with simple restarts

Sometimes you know in advance how you want to change control flow in a loop. Other times you might want to defer that decision by offering a restart. For example, here’s a loop with a pair of with-simple-restarts:

(with-simple-restart (stop-processing "Stop processing users")
  (dolist (user (pending-user-list))
    (with-simple-restart (skip "Skip user ~A" user)
      (process-user user))))

If there is a problem processing users, you might get something in the debugger that looks like this:

Could not find home directory for "nancy"
   [Condition of type SIMPLE-ERROR]

 0: [SKIP] Skip user nancy
 1: [STOP-PROCESSING] Stop processing users

You can then interactively choose which action to take, depending on what’s most appropriate for the condition at hand.

(You can also choose restarts non-interactively. That’s a tip for another day.)

Oct 16

Slow pretty-printing

Producing “pretty” output can be time consuming. Some implementations do a lot of work to determine whether to e.g. break lines when printing with functions like print and format.

If printing is a bottleneck and aesthetic output isn’t required, binding or setting *print-pretty* to NIL can significantly improve output speed for some implementations.

Oct 15

(setf values)

To assign the multiple return values of a function to multiple variables, you could use this:

(multiple-value-setq (whole partial) (truncate x 1024))

(setf values) is more general, and works on places:

(setf (values whole partial) (truncate x 1024))
(setf (values (aref v 0) (aref v 1)) (truncate x 1024))

Oct 14

Fine-grained control flow

The “do” macros (do, do*, dolist, and dotimes) have bodies that act like tagbody. You can put go tags anywhere in the body and use go to jump to them from arbitrary places. This can be useful for skipping, retrying, or otherwise changing the flow of iteration.

For example:

(dolist (users (get-user-list)) 
  (when some-condition
    (go :retry)

Oct 13

Comparing many objects

The numeric comparison functions =, /=, <, <=, >, and >= can take more than two arguments. This is handy to e.g. check if several variables have monotonically increasing numeric value: 

(< a b c d)

For numbers in a sufficiently short list, apply does the trick:

(apply #'< list)

Non-numeric comparison functions generally take exactly two objects to compare, so e.g. (string= x y z) is invalid, and for a list of three or more objects, you can’t just apply the function to the list.

However, you can work your way through the list and perform pairwise comparison with every. For example, to see if all the strings in a list are string=

(every #'string= list (rest list))

Note that /= is special; the following calls are not equivalent:

* (apply #'/= list)
* (every #'/= list (rest list))


Oct 12

:start and :end with parse-integer

parse-integer takes :start and :end arguments, so you don’t have to extract integer subsequences from strings to pass them to parse-integer. For example, to parse date strings that look like “2011-10-01” into year, month, and date integers, you can do this:

(defun parse-date (string)
  "Parse a date string in the form YYYY-MM-DD and return the
   year, month, and day as multiple values."
  (values (parse-integer string :start 0 :end 4)
          (parse-integer string :start 5 :end 7)
          (parse-integer string :start 8 :end 10)))