Looking at the Database Contents
You can also see the current value of *db*
whenever you want by typing *db*
at the REPL.
CL-USER> *db*
((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T)
(:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T)
(:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T))
However, that’s not a very satisfying way of looking at the output. You can write a dump-db
function that dumps out the database in a more human-readable format, like this:
TITLE: Home
ARTIST: Dixie Chicks
RATING: 9
RIPPED: T
TITLE: Fly
ARTIST: Dixie Chicks
RATING: 8
RIPPED: T
TITLE: Roses
ARTIST: Kathy Mattea
RATING: 7
RIPPED: T
The function looks like this:
(defun dump-db ()
(dolist (cd *db*)
(format t "~{~a:~10t~a~%~}~%" cd)))
This function works by looping over all the elements of *db*
with the **DOLIST**
macro, binding each element to the variable cd
in turn. For each value of cd
, you use the **FORMAT**
function to print it.
Admittedly, the **FORMAT**
call is a little cryptic. However, **FORMAT**
isn’t particularly more complicated than C or Perl’s printf
function or Python’s string-%
operator. In Chapter 18 I’ll discuss **FORMAT**
in greater detail. For now we can take this call bit by bit. As you saw in Chapter 2, **FORMAT**
takes at least two arguments, the first being the stream where it sends its output; t
is shorthand for the stream *standard-output*
.
The second argument to **FORMAT**
is a format string that can contain both literal text and directives telling **FORMAT**
things such as how to interpolate the rest of its arguments. Format directives start with ~
(much the way printf
‘s directives start with %
). **FORMAT**
understands dozens of directives, each with their own set of options.3 However, for now I’ll just focus on the ones you need to write dump-db
.
The ~a
directive is the aesthetic directive; it means to consume one argument and output it in a human-readable form. This will render keywords without the leading : and strings without quotation marks. For instance:
CL-USER> (format t "~a" "Dixie Chicks")
Dixie Chicks
NIL
or:
CL-USER> (format t "~a" :title)
TITLE
NIL
The ~t
directive is for tabulating. The ~10t
tells **FORMAT**
to emit enough spaces to move to the tenth column before processing the next ~a
. A ~t
doesn’t consume any arguments.
CL-USER> (format t "~a:~10t~a" :artist "Dixie Chicks")
ARTIST: Dixie Chicks
NIL
Now things get slightly more complicated. When **FORMAT**
sees ~{
the next argument to be consumed must be a list. **FORMAT**
loops over that list, processing the directives between the ~{
and ~
}, consuming as many elements of the list as needed each time through the list. In dump-db
, the **FORMAT**
loop will consume one keyword and one value from the list each time through the loop. The ~%
directive doesn’t consume any arguments but tells **FORMAT**
to emit a newline. Then after the ~
} ends the loop, the last ~%
tells **FORMAT**
to emit one more newline to put a blank line between each CD.
Technically, you could have also used **FORMAT**
to loop over the database itself, turning our dump-db
function into a one-liner.
(defun dump-db ()
(format t "~{~{~a:~10t~a~%~}~%~}" *db*))
That’s either very cool or very scary depending on your point of view.