Unwrapping a Lisp Package
I don’t consider myself a complete n00b to Lisp, but at the same time, I definitely am learning.
One of the greatest features of Lisp is the ability to simply recompile a single function rather than having to save a file and restart an application. Hit C-c, C-c and the function is replaced with your new, correct version. That is, unless it’s in a package.
For instance, when working on my csv reader, I would load the file with C-c l. This would then load the symbols into the image, within the package org.hobbit-hole.csv. I would then use the package, and everything would work fine. Then I would find a bug, fix it in the source, and hit C-c, C-c, which would send the defun to the image. Unfortunately, I would get an error similar to this frequently upon testing:
;;; Warning: Function not defined: CSV-SPLIT-STREAM ;;; An error occurred in function #< COMPILED-FUNCTION: #xD021B8 >: ;;; Error: The function CSV-SPLIT-STREAM is undefined ;;; Entering Corman Lisp debug loop. ;;; Use :C followed by an option to exit. Type :HELP for help. ;;; Restart options: ;;; 1 Abort to top level.
Doh! The problem is that the function csv-split-stream isn’t in the default cl-user package, but is internal to the org.hobbit-hole.csv package. Also inconvenient is that when I received this error, the reader created a symbol in the current package, which means that all subsequent calls to csv-split-stream will use this bogus symbol and get the error, unless the current package is changed or the symbol is uninterned from the current package, something that isn’t always practical to do. Not a huge problem, but a consistent hassle for forgetful programmers such as myself.
Despite the debugging inconvenience, packages are good. Lisp packages allow you to modularize your code, which is a good thing to do no matter what language you use. It therefore makes sense to put your code within packages. So, what are the options when debugging your code?
- Remember to always switch packages first
- Unintern accidental symbols
- Wait to use
defpackageuntil the software needs no more modification - Comment out the defpackage(s) when debugging so everything loads into the current package
The first two are inconvenient, the third never really comes to pass, and the last is good, except I sometimes forget.
Enter reader macros, specifically #+ and #- (sharpsign plus and sharpsign minus in the CLHS). #+ and #- are roughly analogous to #ifdef and #ifndef in C. When a #+:symbol precedes a form, that form is only evaluated when that symbol is present in the *features* list. Vice versa for the #- reader macro.
So, putting these in front of the defpackage and in-package during testing can help you avoid the “Function not defined” error that happens when you’re in the wrong package. Being a standard practice, it is also much easier to remember, similar to remembering to undefine DEBUG when compiling a release version of a C++ program.
So, the top of the csv reader looks like this:
; comment this out when released
#+:cormanlisp
(nconc *features* '(:csv-testing))
(in-package :cl-user)
#-:csv-testing
(defpackage "ORG.HOBBIT-HOLE.CSV"
(:nicknames "CSV")
(:use "CL")
(:export
"ITERATE-CSV-STRING"
"ITERATE-CSV-STREAM")
(:documentation "A csv library. CSV means comma separated
values. It will read a line at a time, and return it as list of separated
strings. Quotes are considered part of a continuous string.
Delimiters other than #\, may be specified, such as tabs.
The following are functions defined for this package (see their
documentation):
iterate-csv-string
iterate-csv-stream"))
#-:csv-testing
(in-package :csv)
#+:csv-testing
(format t "In testing mode~%")
#-:csv-testing
(format t "In release mode~%")
As you can see, I also have a print out that tells me what mode I’ve loaded something in.
There might be some way to use eval-when to automatically do this. I’ll have to do more research.