Common Lisp Tips

Oct 11

Converting characters to integers

If you have the character #\7 and you want the integer 7, you might be tempted to use (parse-integer (string char)) or even this ASCII-oriented technique:

(- (char-int char) (char-int #\0))

While the former is specified to give the right answer, the latter will only work by coincidence. The spec does discuss character ordering, but it makes no guarantees about the values returned by char-int or char-code.

What to use, then? digit-char-p not only returns a true value if its first argument represents a digit, the true value it returns is the integer value of that digit:

* (digit-char-p #\7)
7

It also works with other radixes:

* (digit-char-p #\a 16)
10

If the character is not a digit, digit-char-p returns nil.

Oct 10

Multi-line format control strings

You can break up long format control strings with ~ at the end of a line. For example:

* (format t "It was the best of times, ~
             it was the worst of times.")
It was the best of times, it was the worst of times.

The ~, newline, and all whitespace following the newline are removed from the output, so you can align the continued control string with the previous line.

The : and @ modifiers have additional meaning:

With a :, the newline is ignored, but any following whitespace is left in place. With an @, the newline is left in place, but any following whitespace is ignored.

Oct 09

PAIP lessons

Paradigms of AI Programming is a great book for learning Common Lisp. Peter Norvig wrote a retrospective on the book and included this list of 52 important lessons:

Oct 08

Stylish Common Lisp

The Tutorial on Good Lisp Programming Style by Peter Norvig and Kent Pitman is full of useful tips for Common Lisp programmers.

Oct 07

Touching a file

To create an empty file, like the Unix touch command does, you might try something like this:

;; BOGUS
(close (open "foo.txt" :direction :output 
             :if-does-not-exist :create 
             :if-exists :append))

open has a :direction option specifically for this purpose, though:

(open "foo.txt" :direction :probe :if-does-not-exist :create)

If “foo.txt” does not exist, it will be created. The stream returned is already closed. The spec says this:

[:probe causes] the creation of a “no-directional” file stream; in effect, the file stream is created and then closed prior to being returned by open.

Oct 06

Reading floats

When the reader sees a number like “3.0” with no exponent marker, the reader will convert it into a single-float by default. You can change what float type is used for conversion by changing *read-default-float-format* to another float type.

For example:

* (/ 22.0 7.0)
3.142857

* (setf *read-default-float-format* 'double-float)
DOUBLE-FLOAT

* (/ 22.0 7.0)
3.142857142857143

The printer will also omit exponent markers if the float type of the number being printed matches *read-default-float-format*.

Oct 05

Redirecting output

Got a function that writes to *standard-output* but you really want to redirect it somewhere else? You can bind the *standard-output* special variable in all the macros that create temporary streams.

For example, to return the output as a string:

* (with-output-to-string (*standard-output*) 
    (print-marketing-report))
"Source,Hits
twitter,243
google,805
direct,47
"

To write it out to a file:

* (with-open-file (*standard-output* #p"file.txt" :direction :output)
    (print-marketing-report))
NIL

Oct 04

Swapping places

The naive way to swap the values of two places, a and b, is something like this:

;; BOGUS
(setf temp a)
(setf a b)
(setf b temp)

psetf (parallel setf) can do it in one form:

(psetf a b b a)

But rotatef is best:

(rotatef a b)

Oct 03

Pluralization

The ~P format directive can do simple pluralization.

* (format nil "You have ~D goat~:P." 42)
"You have 42 goats."

* (format nil "You have ~D goat~:P." 1)
"You have 1 goat."

* (format nil "You have ~D fl~@:P." 42)
"You have 42 flies."

* (format nil "You have ~D fl~@:P." 1)
"You have 1 fly."

Irregular plurals are more complicated:

(format nil "You have ~D ~:*~[mice~;mouse~:;mice~]." n)

(Thanks to stassats for the flies and mice.)

Oct 02

string output streams

get-output-stream-string doesn’t just return the string accumulated so far in a string-stream. It also resets the accumulation, so you can use the same stream multiple times for separate results. Here’s a simple string splitter:

(defun split (string &optional (split-character #\Space))
  (let ((result '())
        (stream (make-string-output-stream)))
    (loop for char across string
          if (char= char split-character)
          do (push (get-output-stream-string stream) result)
          else
          do (write-char char stream))
    (push (get-output-stream-string stream) result)
    (nreverse result)))

It could be used like this:

* (split "The quick brown fox.")
("The" "quick" "brown" "fox.")

I learned about this technique from an Erik Naggum post.