((:TITLE 'Fly' :ARTIST 'Dixie Chicks' :RATING 8 :RIPPED T)

(:TITLE 'Roses' :ARTIST 'Kathy Mattea' :RATING 7 :RIPPED T))

CL-USER> (add-record (make-cd 'Home' 'Dixie Chicks' 9 t))

((: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 stuff printed by the REPL after each call to add-record is the return value, which is the value returned by the last expression in the function body, the PUSH. And PUSH returns the new value of the variable it's modifying. So what you're actually seeing is the value of the database after the record has been added.

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.[26] 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.

Improving the User Interaction

While our add-record function works fine for adding records, it's a bit Lispy for the casual user. And if they want to add a bunch of records, it's not very convenient. So you may want to write a function to prompt the user for information about a set of CDs. Right away you know you'll need some way to prompt the user for a piece of information and read it. So let's write that.

(defun prompt-read (prompt)

(format *query-io* '~a: ' prompt)

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату