Everyone writes new methods for print-object for specialized printing of their own objects. But the output of describe can be specialized as well via describe-object. For example, if you have an object that has a vector of keys and a corresponding vector of values, the standard describe output might not show them in a way that’s very readable:
* (describe record)
#<RECORD {1002A87A03}>
[standard-object]
Slots with :INSTANCE allocation:
SOURCE = #P"data.txt"
ID = 47
KEYS = #("Rating" "Unit Price" "Description" "Acquired Date")
VALS = #(7.2 21 "Blue-fringed tarpaulin with star pattern"..
You can add your own primary or auxiliary methods to change what describe prints:
(defmethod describe-object :after ((record record) stream)
(write-line "Data values:")
(loop for key across (keys record)
for val across (vals record)
do (format stream " ~A: ~A~%" key val)))
Then the describe output can be more readable:
#<RECORD {1002B54023}>
[standard-object]
Slots with :INSTANCE allocation:
SOURCE = #P"data.txt"
ID = 47
KEYS = #("Rating" "Unit Price" "Description" "Acquired Date")
VALS = #(7.2 21 "Blue-fringed tarpaulin with star pattern"..
Data values:
Rating: 7.2
Unit Price: 21
Description: Blue-fringed tarpaulin with star pattern
Acquired Date: 3532942160
Many CL implementations (SBCL in particular) perform output in a buffered manner. Sometimes this may cause a confusion, because it may reverse the order of effects. Like in the following example it may be possible to enter something before seeing ‘?’.
(defun ask ()
(princ '?)
(read))
Also if you are writing an application, that should interact with some other program (for example over a pipe) and there are hard limits on the interaction speed, buffering can be a problem.
For such cases there’s a standard function finish-output, that initiates dumping any buffered output. There’s also force-output, that does the same thing, but asynchronously (i.e. doesn’t wait for the actual writing to finish).
Common Lisp has relatively few functions that work exclusively on strings. There is no function, for example, to extract a substring from a string. However, strings are defined as specialized vectors of character objects. Since vectors are sequences, all the sequence-related functions also apply to strings. To extract a substring from a string, use subseq.
If you pass a stream to a library function that closes it (as with with-open-stream), but you want the stream to remain open, you can wrap the stream in a concatenated stream:
(auto-closing-function (make-concatenated-stream my-stream))
When the concatenated stream is closed, the wrapped stream my-stream remains open.
(Thanks to Rob Warnock for this tip.)
What to use if you have a multidimensional array and you want to do something to each element?
row-major-aref can access an element in a multidimensional array with a single index. array-total-size returns the total number of elements in an array. Together, you can do something like this:
(dotimes (i (array-total-size array))
(incf (row-major-aref array i) 42))
You can also displace a one-dimensional array to the multidimensional array and work on it with sequence or vector functions:
(let ((vector (make-array (array-total-size array) :displaced-to array)))
(fill vector 42))
Novices who want an array of distinct things sometimes write something like this, where make-foo returns a fresh object of some sort:
(make-array 42 :initial-element (make-foo))
This actually creates a vector containing the same identical (eq) object at all 42 indexes. The initial-element argument is evaluated only once to produce the result, and the result is used 42 times to initialize the vector.
42 distinct things can be obtained like this:
(map-into (make-array 42) #'make-foo)
map-into calls make-foo 42 times and returns the initialized array as its result.
If you’re trying to learn Common Lisp from Paul Graham’s ANSI Common Lisp (which I don’t recommend), be sure to read Graham Crackers. These course notes outline some of the cases where the style of ANSI Common Lisp differs from good Common Lisp style.
floor, truncate, and related functions can take an optional second argument to use as the divisor. The default value is the integer 1. Instead of (floor (/ x 42)) you can use (floor x 42).
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 ()
((command
:initarg :command
:accessor command)
(checkout-subcommand
: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)
((command
:initform "git")
(checkout-subcommand
:initform "clone")))
(defclass vc-cvs (vc-command)
((command
:initform "cvs")
(checkout-subcommand
:initform "co")))
I prefer to use :default-initargs to avoid recapping the slots:
(defclass vc-git (vc-command)
()
(:default-initargs
:command "git"
:checkout-subcommand "clone"))
(defclass vc-cvs (vc-command)
()
(:default-initargs
: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)
()
(:default-initargs
:persist nil))
(defmethod initialize-instance :after ((instance dibble) &key persist)
(when persist
(persist-object instance)))
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.