For notes on dpb, please see the earlier articles on byte and deposit-field.
Monthly Archives: January 2014
The less-familiar parts of Lisp for beginners — do-symbols
Now we move on to a set of three macros, do-all-symbols, do-external-symbols, and do-symbols. These macros allow the code to iterate over a list of symbols as they are known to the Lisp image. The programmer might use these to inspect the execution environment and possibly call code in optional libraries, but there are usually easier ways to achieve that. One place that I have seen it used is similar to the use of weak symbols in the C++ linker. A piece of code can use do-external-symbols to detect collisions and import other symbols into its namespace only when there is no existing function/variable with the same name. The assumption is that there might be some very basic interface that is optionally extended in another file. The extended package can pick out those functions and variables from the basic package that are not re-implemented. Naturally, this requires very tight cooperation between the two interfaces, but I’ve seen it done.
What do these functions do? Well, do-symbols iterates over all symbols available to a package (or the current package if none is supplied). Generally, that’s all the symbols that the package creates, plus all the external symbols in other packages, including all the symbols in the Lisp language itself.
The do-external-symbols macro iterates over all symbols exported by the package (or the current package if none is supplied). This is the one you’re most likely to use.
The do-all-symbols macro iterates over all symbols in all packages. It’s a symbol dump of the Lisp image itself.
Note that do-symbols and do-all-symbols may list some symbols more than once, so if your goal is to visit each symbol exactly once, you’ll have to take steps to skip the duplicates.
The less-familiar parts of Lisp for beginners — deposit-field
Now we move on to deposit-field. A cursory introduction to Lisp is unlikely to have discussed this function. To begin, I’d suggest that you review the earlier article on byte. The deposit-field function is used to remove a bitfield from one integer, SRC, and insert it into another integer, DST, returning the result. How is this different from dpb, whose effects were shown in that article on byte? In dpb, the byte specifier acts only to window DST. That is, if the byte specifier is 5 bits wide, then the lowest 5 bits of SRC are used, regardless of the position of the byte specifier. In deposit-field, SRC and DST are both windowed by the byte specifier, so it is possible to insert bits other than the least-significant bits of SRC.
This sort of bitfield manipulation is not used very frequently. It’s essential when writing code to certain binary protocols, very useful for device drivers, network stacks, and things like that, but less commonly used in other applications.
The less-familiar parts of Lisp for beginners — delete-package
Our next obscure Lisp command for newcomers arriving from C++ is delete-package. To begin, I’ll discuss what a package behaves like from the perspective of C++.
A package is a bit like a C++ namespace, but with a few extra twists. In C++, a namespace is essentially a name-mangling tool. It allows the programmer to create symbols without worrying too much about symbol name collisions. In Lisp, a package has this quality, but also other properties. First of all, a Lisp package is a real obect in the language, not simply a compiler directive for symbol naming. The package is, very broadly, a symbol container. Secondly, packages can inherit from other packages, and can therefore inherit symbols or override them as necessary. Don’t confuse a package with a class, though, as you can define many classes or no classes in a package, and there isn’t an equivalent to polymorphism in the syntax of package symbol resolution, the way there is with class hierarchies and generic functions.
So, what does delete-package do? Well, perhaps unsurprisingly, it deletes the package. The result is that the classes and functions defined in the package are no longer visible in the Lisp environment. Once again, the need for this is due to the fact that Lisp programs run in a Lisp environment, and not as self-contained things that are compiled and then executed.
For the newcomer, when might you need delete-package? Most obviously, during development. If you’re working in a package, and you’ve defined some generic functions, only to decide later that you need to change the interface of a generic function and the methods that implement it, your Lisp instance will complain when you try to re-load the package, because redefining the generic function will bring it into conflict with the implemented methods. By calling delete-package before reloading the modified file, you delete the changes that the package made to the Lisp system, and so can safely load the file. Other cases where you might need to delete the package before loading it include when you’ve got a defconstant on a list (see the earlier discussion there), or when you’ve used defvar in the package. Really, any time when the state of the package might modify the effect of loading the package again.
The less-familiar parts of Lisp for beginners — deftype
Moving onwards, the casual Lisp programmer arriving from C++ might not have encountered deftype in his or her reading. There’s a fine description and example of the macro in the CLHS. This macro allows the programmer to define a new type specifier.
So, why might the programmer want to create new type specifiers? Most commonly, programmers either want to use them along with declare and the to provide compiler hints, helping the optimizer to know what underlying types are being used. The programmer-defined type specifier is somewhat similar to a C++ typedef, by defining your own type specifier you can easily alter code when you have to change the underlying type, without having to search through your code for occurrences of the type.
New type specifiers also might be useful in typecase statements:
(defun equidimensional (a) (or (< (array-rank a) 2) (apply #'= (array-dimensions a)))) (defun all-ints (a) (let ((len (array-total-size a))) (dotimes (i len) (unless (integerp (row-major-aref a i)) (return-from all-ints nil))) t)) (deftype square-matrix (&optional type size) `(and (array ,type (,size ,size)) (satisfies equidimensional))) (deftype integer-square-matrix (&optional size) `(and (square-matrix integer ,size) (satisfies all-ints))) (defun demonstrate () (let ((objects (list :ABC (make-array '(2 3)) (make-array '(2 2) :initial-element 1) (make-array '(2 2) :initial-element 1.1)))) (dolist (obj objects) (format t "~S~26T" obj) (typecase obj (integer-square-matrix (format t "is an integer square matrix")) (square-matrix (format t "is a square matrix")) (array (format t "is a non-square matrix")) (t (format t "is a non-matrix"))) (format t "~%"))))
Producing output:
CL-USER> (demonstrate) :ABC is a non-matrix #2A((0 0 0) (0 0 0)) is a non-square matrix #2A((1 1) (1 1)) is an integer square matrix #2A((1.1 1.1) (1.1 1.1)) is a square matrix NIL