709 lines
55 KiB
HTML
709 lines
55 KiB
HTML
|
<HTML><HEAD><TITLE>Practical: A Simple Database</TITLE><LINK REL="stylesheet" TYPE="text/css" HREF="style.css"/></HEAD><BODY><DIV CLASS="copyright">Copyright © 2003-2005, Peter Seibel</DIV><H1>3. Practical: A Simple Database</H1><P>Obviously, before you can start building real software in Lisp,
|
||
|
you'll have to learn the language. But let's face it--you may be
|
||
|
thinking, "'Practical Common Lisp,' isn't that an oxymoron? Why
|
||
|
should you be expected to bother learning all the details of a
|
||
|
language unless it's actually good for something you care about?" So
|
||
|
I'll start by giving you a small example of what you can do with
|
||
|
Common Lisp. In this chapter you'll write a simple database for
|
||
|
keeping track of CDs. You'll use similar techniques in Chapter 27
|
||
|
when you build a database of MP3s for our streaming MP3 server. In
|
||
|
fact, you could think of this as part of the MP3 software
|
||
|
project--after all, in order to have a bunch of MP3s to listen to, it
|
||
|
might be helpful to be able to keep track of which CDs you have and
|
||
|
which ones you need to rip.</P><P>In this chapter, I'll cover just enough Lisp as we go along for you
|
||
|
to understand how the code works. But I'll gloss over quite a few
|
||
|
details. For now you needn't sweat the small stuff--the next several
|
||
|
chapters will cover all the Common Lisp constructs used here, and
|
||
|
more, in a much more systematic way.</P><P>One terminology note: I'll discuss a handful of Lisp operators in
|
||
|
this chapter. In Chapter 4, you'll learn that Common Lisp provides
|
||
|
three distinct kinds of operators: <I>functions</I>, <I>macros</I>, and
|
||
|
<I>special operators</I>. For the purposes of this chapter, you don't
|
||
|
really need to know the difference. I will, however, refer to
|
||
|
different operators as functions or macros or special operators as
|
||
|
appropriate, rather than trying to hide the details behind the word
|
||
|
<I>operator</I>. For now you can treat <I>function</I>, <I>macro</I>, and
|
||
|
<I>special operator</I> as all more or less equivalent.<SUP>1</SUP></P><P>Also, keep in mind that I won't bust out all the most sophisticated
|
||
|
Common Lisp techniques for your very first post-"hello, world"
|
||
|
program. The point of this chapter isn't that this is how you would
|
||
|
write a database in Lisp; rather, the point is for you to get an idea
|
||
|
of what programming in Lisp is like and to see how even a relatively
|
||
|
simple Lisp program can be quite featureful.</P><A NAME="cds-and-records"><H2>CDs and Records</H2></A><P>To keep track of CDs that need to be ripped to MP3s and which CDs
|
||
|
should be ripped first, each record in the database will contain the
|
||
|
title and artist of the CD, a rating of how much the user likes it,
|
||
|
and a flag saying whether it has been ripped. So, to start with,
|
||
|
you'll need a way to represent a single database record (in other
|
||
|
words, one CD). Common Lisp gives you lots of choices of data
|
||
|
structures from a simple four-item list to a user-defined class,
|
||
|
using the Common Lisp Object System (CLOS).</P><P>For now you can stay at the simple end of the spectrum and use a
|
||
|
list. You can make a list with the <CODE><B>LIST</B></CODE> function, which,
|
||
|
appropriately enough, returns a list of its arguments.</P><PRE>CL-USER> (list 1 2 3)
|
||
|
(1 2 3)</PRE><P>You could use a four-item list, mapping a given position in the list
|
||
|
to a given field in the record. However, another flavor of
|
||
|
list--called a <I>property list</I>, or <I>plist</I> for short--is even
|
||
|
more convenient. A plist is a list where every other element,
|
||
|
starting with the first, is a <I>symbol</I> that describes what the next
|
||
|
element in the list is. I won't get into all the details of exactly
|
||
|
what a symbol is right now; basically it's a name. For the symbols
|
||
|
that name the fields in the CD database, you can use a particular
|
||
|
kind of symbol, called a <I>keyword</I> symbol. A keyword is any name
|
||
|
that starts with a colon (<CODE>:</CODE>), for instance, <CODE>:foo</CODE>.
|
||
|
Here's an example of a plist using the keyword symbols <CODE>:a</CODE>,
|
||
|
<CODE>:b</CODE>, and <CODE>:c</CODE> as property names:</P><PRE>CL-USER> (list :a 1 :b 2 :c 3)
|
||
|
(:A 1 :B 2 :C 3)</PRE><P>Note that you can create a property list with the same <CODE><B>LIST</B></CODE>
|
||
|
function as you use to create other lists; it's the contents that
|
||
|
make it a plist.</P><P>The thing that makes plists a convenient way to represent the records
|
||
|
in a database is the function <CODE><B>GETF</B></CODE>, which takes a plist and a
|
||
|
symbol and returns the value in the plist following the symbol,
|
||
|
making a plist a sort of poor man's hash table. Lisp has real hash
|
||
|
tables too, but plists are sufficient for your needs here and can
|
||
|
more easily be saved to a file, which will come in handy later. </P><PRE>CL-USER> (getf (list :a 1 :b 2 :c 3) :a)
|
||
|
1
|
||
|
CL-USER> (getf (list :a 1 :b 2 :c 3) :c)
|
||
|
3</PRE><P>Given all that, you can easily enough write a function <CODE>make-cd</CODE>
|
||
|
that will take the four fields as arguments and return a plist
|
||
|
representing that CD.</P><PRE>(defun make-cd (title artist rating ripped)
|
||
|
(list :title title :artist artist :rating rating :ripped ripped))</PRE><P>The word <CODE><B>DEFUN</B></CODE> tells us that this form is defining a new
|
||
|
function. The name of the function is <CODE>make-cd</CODE>. After the name
|
||
|
comes the parameter list. This function has four parameters:
|
||
|
<CODE>title</CODE>, <CODE>artist</CODE>, <CODE>rating</CODE>, and <CODE>ripped</CODE>.
|
||
|
Everything after the parameter list is the body of the function. In
|
||
|
this case the body is just one form, a call to <CODE><B>LIST</B></CODE>. When
|
||
|
<CODE>make-cd</CODE> is called, the arguments passed to the call will be
|
||
|
bound to the variables in the parameter list. For instance, to make a
|
||
|
record for the CD <I>Roses</I> by Kathy Mattea, you might call
|
||
|
<CODE>make-cd</CODE> like this:</P><PRE>CL-USER> (make-cd "Roses" "Kathy Mattea" 7 t)
|
||
|
(:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T) </PRE><A NAME="filing-cds"><H2>Filing CDs</H2></A><P>A single record, however, does not a database make. You need some
|
||
|
larger construct to hold the records. Again, for simplicity's sake, a
|
||
|
list seems like a good choice. Also for simplicity you can use a
|
||
|
global variable, <CODE>*db*</CODE>, which you can define with the
|
||
|
<CODE><B>DEFVAR</B></CODE> macro. The asterisks (*) in the name are a Lisp naming
|
||
|
convention for global variables.<SUP>2</SUP></P><PRE>(defvar *db* nil)</PRE><P>You can use the <CODE><B>PUSH</B></CODE> macro to add items to <CODE>*db*</CODE>. But it's
|
||
|
probably a good idea to abstract things a tiny bit, so you should
|
||
|
define a function <CODE>add-record</CODE> that adds a record to the
|
||
|
database.</P><PRE>(defun add-record (cd) (push cd *db*))</PRE><P>Now you can use <CODE>add-record</CODE> and <CODE>make-cd</CODE> together to add
|
||
|
CDs to the database.</P><PRE>CL-USER> (add-record (make-cd "Roses" "Kathy Mattea" 7 t))
|
||
|
((:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T))
|
||
|
CL-USER> (add-record (make-cd "Fly" "Dixie Chicks" 8 t))
|
||
|
((: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))</PRE><P>The stuff printed by the REPL after each call to <CODE>add-record</CODE> is
|
||
|
the return value, which is the value returned by the last expression
|
||
|
in the function body, the <CODE><B>PUSH</B></CODE>. And <CODE><B>PUSH</B></CODE> 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. </P><A NAME="looking-at-the-database-contents"><H2>Looking at the Database Contents</H2></A><P>You can also see the current value of <CODE>*db*</CODE> whenever you want
|
||
|
by typing <CODE>*db*</CODE> at the REPL.</P><PRE>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))</PRE><P>However, that's not a very satisfying way of looking at the output.
|
||
|
You can write a <CODE>dump-db</CODE> function that dumps out the database
|
||
|
in a more human-readable format, like this:</P><PRE>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</PRE><P>The function looks like this:</P><PRE>(defun dump-db ()
|
||
|
(dolist (cd *db*)
|
||
|
(format t "~{~a:~10t~a~%~}~%" cd)))</PRE><P>This function works by looping over all the elements of <CODE>*db*</CODE>
|
||
|
with the <CODE><B>DOLIST</B></CODE> macro, binding each element to the variable
|
||
|
<CODE>cd</CODE> in turn. For each value of <CODE>cd</CODE>, you use the
|
||
|
<CODE><B>FORMAT</B></CODE> function to print it. </P><P>Admittedly, the <CODE><B>FORMAT</B></CODE> call is a little cryptic. However,
|
||
|
<CODE><B>FORMAT</B></CODE> isn't particularly more complicated than C or Perl's
|
||
|
<CODE>printf</CODE> function or Python's string-<CODE>%</CODE> operator. In
|
||
|
Chapter 18 I'll discuss <CODE><B>FORMAT</B></CODE> in greater detail. For now we can
|
||
|
take this call bit by bit. As you saw in Chapter 2, <CODE><B>FORMAT</B></CODE> takes
|
||
|
at least two arguments, the first being the stream where it sends its
|
||
|
output; <CODE>t</CODE> is shorthand for the stream
|
||
|
<CODE>*standard-output*</CODE>.</P><P>The second argument to <CODE><B>FORMAT</B></CODE> is a format string that can
|
||
|
contain both literal text and directives telling <CODE><B>FORMAT</B></CODE> things
|
||
|
such as how to interpolate the rest of its arguments. Format
|
||
|
directives start with <CODE>~</CODE> (much the way <CODE>printf</CODE>'s
|
||
|
directives start with <CODE>%</CODE>). <CODE><B>FORMAT</B></CODE> understands dozens of
|
||
|
directives, each with their own set of options.<SUP>3</SUP> However, for now I'll just focus on the ones you need to write
|
||
|
<CODE>dump-db</CODE>.</P><P>The <CODE>~a</CODE> directive is the <I>aesthetic</I> 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:</P><PRE>CL-USER> (format t "~a" "Dixie Chicks")
|
||
|
Dixie Chicks
|
||
|
NIL</PRE><P>or:</P><PRE>CL-USER> (format t "~a" :title)
|
||
|
TITLE
|
||
|
NIL</PRE><P>The <CODE>~t</CODE> directive is for tabulating. The <CODE>~10t</CODE> tells
|
||
|
<CODE><B>FORMAT</B></CODE> to emit enough spaces to move to the tenth column before
|
||
|
processing the next <CODE>~a</CODE>. A <CODE>~t</CODE> doesn't consume any
|
||
|
arguments. </P><PRE>CL-USER> (format t "~a:~10t~a" :artist "Dixie Chicks")
|
||
|
ARTIST: Dixie Chicks
|
||
|
NIL</PRE><P>Now things get slightly more complicated. When <CODE><B>FORMAT</B></CODE> sees
|
||
|
<CODE>~{</CODE> the next argument to be consumed must be a list.
|
||
|
<CODE><B>FORMAT</B></CODE> loops over that list, processing the directives between
|
||
|
the <CODE>~{</CODE> and <CODE>~</CODE>}, consuming as many elements of the list
|
||
|
as needed each time through the list. In <CODE>dump-db</CODE>, the
|
||
|
<CODE><B>FORMAT</B></CODE> loop will consume one keyword and one value from the list
|
||
|
each time through the loop. The <CODE>~%</CODE> directive doesn't consume
|
||
|
any arguments but tells <CODE><B>FORMAT</B></CODE> to emit a newline. Then after the
|
||
|
<CODE>~</CODE>} ends the loop, the last <CODE>~%</CODE> tells <CODE><B>FORMAT</B></CODE> to emit
|
||
|
one more newline to put a blank line between each CD. </P><P>Technically, you could have also used <CODE><B>FORMAT</B></CODE> to loop over the
|
||
|
database itself, turning our <CODE>dump-db</CODE> function into a
|
||
|
one-liner.</P><PRE>(defun dump-db ()
|
||
|
(format t "~{~{~a:~10t~a~%~}~%~}" *db*))</PRE><P>That's either very cool or very scary depending on your point of
|
||
|
view. </P><A NAME="improving-the-user-interaction"><H2>Improving the User Interaction</H2></A><P>While our <CODE>add-record</CODE> 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.</P><PRE>(defun prompt-read (prompt)
|
||
|
(format *query-io* "~a: " prompt)
|
||
|
(force-output *query-io*)
|
||
|
(read-line *query-io*))</PRE><P>You use your old friend <CODE><B>FORMAT</B></CODE> to emit a prompt. Note that
|
||
|
there's no <CODE>~%</CODE> in the format string, so the cursor will stay on
|
||
|
the same line. The call to <CODE><B>FORCE-OUTPUT</B></CODE> is necessary in some
|
||
|
implementations to ensure that Lisp doesn't wait for a newline before
|
||
|
it prints the prompt.</P><P>Then you can read a single line of text with the aptly named
|
||
|
<CODE><B>READ-LINE</B></CODE> function. The variable <CODE>*query-io*</CODE> is a global
|
||
|
variable (which you can tell because of the <CODE>*</CODE> naming
|
||
|
convention for global variables) that contains the input stream
|
||
|
connected to the terminal. The return value of <CODE>prompt-read</CODE>
|
||
|
will be the value of the last form, the call to <CODE><B>READ-LINE</B></CODE>, which
|
||
|
returns the string it read (without the trailing newline.)</P><P>You can combine your existing <CODE>make-cd</CODE> function with
|
||
|
<CODE>prompt-read</CODE> to build a function that makes a new CD record
|
||
|
from data it gets by prompting for each value in turn. </P><PRE>(defun prompt-for-cd ()
|
||
|
(make-cd
|
||
|
(prompt-read "Title")
|
||
|
(prompt-read "Artist")
|
||
|
(prompt-read "Rating")
|
||
|
(prompt-read "Ripped [y/n]")))</PRE><P>That's almost right. Except <CODE>prompt-read</CODE> returns a string,
|
||
|
which, while fine for the Title and Artist fields, isn't so great for
|
||
|
the Rating and Ripped fields, which should be a number and a boolean.
|
||
|
Depending on how sophisticated a user interface you want, you can go
|
||
|
to arbitrary lengths to validate the data the user enters. For now
|
||
|
let's lean toward the quick and dirty: you can wrap the
|
||
|
<CODE>prompt-read</CODE> for the rating in a call to Lisp's
|
||
|
<CODE><B>PARSE-INTEGER</B></CODE> function, like this:</P><PRE>(parse-integer (prompt-read "Rating"))</PRE><P>Unfortunately, the default behavior of <CODE><B>PARSE-INTEGER</B></CODE> is to
|
||
|
signal an error if it can't parse an integer out of the string or if
|
||
|
there's any non-numeric junk in the string. However, it takes an
|
||
|
optional keyword argument <CODE>:junk-allowed</CODE>, which tells it to
|
||
|
relax a bit.</P><PRE>(parse-integer (prompt-read "Rating") :junk-allowed t)</PRE><P>But there's still one problem: if it can't find an integer amidst all
|
||
|
the junk, <CODE><B>PARSE-INTEGER</B></CODE> will return <CODE>NIL</CODE> rather than a
|
||
|
number. In keeping with the quick-and-dirty approach, you may just
|
||
|
want to call that 0 and continue. Lisp's <CODE><B>OR</B></CODE> macro is just the
|
||
|
thing you need here. It's similar to the "short-circuiting" <CODE>||</CODE>
|
||
|
in Perl, Python, Java, and C; it takes a series of expressions,
|
||
|
evaluates them one at a time, and returns the first non-nil value (or
|
||
|
<CODE><B>NIL</B></CODE> if they're all <CODE><B>NIL</B></CODE>). So you can use the following: </P><PRE>(or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)</PRE><P>to get a default value of 0.</P><P>Fixing the code to prompt for Ripped is quite a bit simpler. You can
|
||
|
just use the Common Lisp function <CODE><B>Y-OR-N-P</B></CODE>.</P><PRE>(y-or-n-p "Ripped [y/n]: ")</PRE><P>In fact, this will be the most robust part of <CODE>prompt-for-cd</CODE>,
|
||
|
as <CODE><B>Y-OR-N-P</B></CODE> will reprompt the user if they enter something that
|
||
|
doesn't start with <I>y</I>, <I>Y</I>, <I>n</I>, or <I>N</I>.</P><P>Putting those pieces together you get a reasonably robust
|
||
|
<CODE>prompt-for-cd</CODE> function.</P><PRE>(defun prompt-for-cd ()
|
||
|
(make-cd
|
||
|
(prompt-read "Title")
|
||
|
(prompt-read "Artist")
|
||
|
(or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
|
||
|
(y-or-n-p "Ripped [y/n]: ")))</PRE><P>Finally, you can finish the "add a bunch of CDs" interface by
|
||
|
wrapping <CODE>prompt-for-cd</CODE> in a function that loops until the user
|
||
|
is done. You can use the simple form of the <CODE><B>LOOP</B></CODE> macro, which
|
||
|
repeatedly executes a body of expressions until it's exited by a call
|
||
|
to <CODE><B>RETURN</B></CODE>. For example: </P><PRE>(defun add-cds ()
|
||
|
(loop (add-record (prompt-for-cd))
|
||
|
(if (not (y-or-n-p "Another? [y/n]: ")) (return))))</PRE><P>Now you can use <CODE>add-cds</CODE> to add some more CDs to the database.</P><PRE>CL-USER> (add-cds)
|
||
|
Title: Rockin' the Suburbs
|
||
|
Artist: Ben Folds
|
||
|
Rating: 6
|
||
|
Ripped [y/n]: y
|
||
|
Another? [y/n]: y
|
||
|
Title: Give Us a Break
|
||
|
Artist: Limpopo
|
||
|
Rating: 10
|
||
|
Ripped [y/n]: y
|
||
|
Another? [y/n]: y
|
||
|
Title: Lyle Lovett
|
||
|
Artist: Lyle Lovett
|
||
|
Rating: 9
|
||
|
Ripped [y/n]: y
|
||
|
Another? [y/n]: n
|
||
|
NIL</PRE><A NAME="saving-and-loading-the-database"><H2>Saving and Loading the Database</H2></A><P>Having a convenient way to add records to the database is nice. But
|
||
|
it's not so nice that the user is going to be very happy if they have
|
||
|
to reenter all the records every time they quit and restart Lisp.
|
||
|
Luckily, with the data structures you're using to represent the data,
|
||
|
it's trivially easy to save the data to a file and reload it later.
|
||
|
Here's a <CODE>save-db</CODE> function that takes a filename as an argument
|
||
|
and saves the current state of the database:</P><PRE>(defun save-db (filename)
|
||
|
(with-open-file (out filename
|
||
|
:direction :output
|
||
|
:if-exists :supersede)
|
||
|
(with-standard-io-syntax
|
||
|
(print *db* out))))</PRE><P>The <CODE><B>WITH-OPEN-FILE</B></CODE> macro opens a file, binds the stream to a
|
||
|
variable, executes a set of expressions, and then closes the file. It
|
||
|
also makes sure the file is closed even if something goes wrong while
|
||
|
evaluating the body. The list directly after <CODE><B>WITH-OPEN-FILE</B></CODE>
|
||
|
isn't a function call but rather part of the syntax defined by
|
||
|
<CODE><B>WITH-OPEN-FILE</B></CODE>. It contains the name of the variable that will
|
||
|
hold the file stream to which you'll write within the body of
|
||
|
<CODE><B>WITH-OPEN-FILE</B></CODE>, a value that must be a file name, and then some
|
||
|
options that control how the file is opened. Here you specify that
|
||
|
you're opening the file for writing with <CODE>:direction :output</CODE>
|
||
|
and that you want to overwrite an existing file of the same name if
|
||
|
it exists with <CODE>:if-exists :supersede</CODE>. </P><P>Once you have the file open, all you have to do is print the contents
|
||
|
of the database with <CODE>(print *db* out)</CODE>. Unlike <CODE><B>FORMAT</B></CODE>,
|
||
|
<CODE><B>PRINT</B></CODE> prints Lisp objects in a form that can be read back in by
|
||
|
the Lisp reader. The macro <CODE><B>WITH-STANDARD-IO-SYNTAX</B></CODE> ensures that
|
||
|
certain variables that affect the behavior of <CODE><B>PRINT</B></CODE> are set to
|
||
|
their standard values. You'll use the same macro when you read the
|
||
|
data back in to make sure the Lisp reader and printer are operating
|
||
|
compatibly.</P><P>The argument to <CODE>save-db</CODE> should be a string containing the name
|
||
|
of the file where the user wants to save the database. The exact form
|
||
|
of the string will depend on what operating system they're using. For
|
||
|
instance, on a Unix box they should be able to call <CODE>save-db</CODE>
|
||
|
like this: </P><PRE>CL-USER> (save-db "~/my-cds.db")
|
||
|
((:TITLE "Lyle Lovett" :ARTIST "Lyle Lovett" :RATING 9 :RIPPED T)
|
||
|
(:TITLE "Give Us a Break" :ARTIST "Limpopo" :RATING 10 :RIPPED T)
|
||
|
(:TITLE "Rockin' the Suburbs" :ARTIST "Ben Folds" :RATING 6 :RIPPED
|
||
|
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 9 :RIPPED T))</PRE><P>On Windows, the filename might be something like
|
||
|
"<CODE>c:/my-cds.db</CODE>" or "<CODE>c:\\my-cds.db</CODE>."<SUP>4</SUP></P><P>You can open this file in any text editor to see what it looks like.
|
||
|
You should see something a lot like what the REPL prints if you type
|
||
|
<CODE>*db*</CODE>.</P><P>The function to load the database back in is similar.</P><PRE>(defun load-db (filename)
|
||
|
(with-open-file (in filename)
|
||
|
(with-standard-io-syntax
|
||
|
(setf *db* (read in)))))</PRE><P>This time you don't need to specify <CODE>:direction</CODE> in the options
|
||
|
to <CODE><B>WITH-OPEN-FILE</B></CODE>, since you want the default of <CODE>:input</CODE>.
|
||
|
And instead of printing, you use the function <CODE><B>READ</B></CODE> to read from
|
||
|
the stream <CODE>in</CODE>. This is the same reader used by the REPL and
|
||
|
can read any Lisp expression you could type at the REPL prompt.
|
||
|
However, in this case, you're just reading and saving the expression,
|
||
|
not evaluating it. Again, the <CODE><B>WITH-STANDARD-IO-SYNTAX</B></CODE> macro
|
||
|
ensures that <CODE><B>READ</B></CODE> is using the same basic syntax that
|
||
|
<CODE>save-db</CODE> did when it <CODE><B>PRINT</B></CODE>ed the data.</P><P>The <CODE><B>SETF</B></CODE> macro is Common Lisp's main assignment operator. It
|
||
|
sets its first argument to the result of evaluating its second
|
||
|
argument. So in <CODE>load-db</CODE> the <CODE>*db*</CODE> variable will contain
|
||
|
the object read from the file, namely, the list of lists written by
|
||
|
<CODE>save-db</CODE>. You do need to be careful about one
|
||
|
thing--<CODE>load-db</CODE> clobbers whatever was in <CODE>*db*</CODE> before the
|
||
|
call. So if you've added records with <CODE>add-record</CODE> or
|
||
|
<CODE>add-cds</CODE> that haven't been saved with <CODE>save-db</CODE>, you'll
|
||
|
lose them. </P><A NAME="querying-the-database"><H2>Querying the Database</H2></A><P>Now that you have a way to save and reload the database to go along
|
||
|
with a convenient user interface for adding new records, you soon may
|
||
|
have enough records that you won't want to be dumping out the whole
|
||
|
database just to look at what's in it. What you need is a way to
|
||
|
query the database. You might like, for instance, to be able to write
|
||
|
something like this:</P><PRE>(select :artist "Dixie Chicks")</PRE><P>and get a list of all the records where the artist is the Dixie
|
||
|
Chicks. Again, it turns out that the choice of saving the records in
|
||
|
a list will pay off.</P><P>The function <CODE><B>REMOVE-IF-NOT</B></CODE> takes a predicate and a list and
|
||
|
returns a list containing only the elements of the original list that
|
||
|
match the predicate. In other words, it has removed all the elements
|
||
|
that don't match the predicate. However, <CODE><B>REMOVE-IF-NOT</B></CODE> doesn't
|
||
|
really remove anything--it creates a new list, leaving the original
|
||
|
list untouched. It's like running grep over a file. The predicate
|
||
|
argument can be any function that accepts a single argument and
|
||
|
returns a boolean value--<CODE><B>NIL</B></CODE> for false and anything else for
|
||
|
true. </P><P>For instance, if you wanted to extract all the even elements from a
|
||
|
list of numbers, you could use <CODE><B>REMOVE-IF-NOT</B></CODE> as follows:</P><PRE>CL-USER> (remove-if-not #'evenp '(1 2 3 4 5 6 7 8 9 10))
|
||
|
(2 4 6 8 10)</PRE><P>In this case, the predicate is the function <CODE><B>EVENP</B></CODE>, which returns
|
||
|
true if its argument is an even number. The funny notation <CODE>#'</CODE>
|
||
|
is shorthand for "Get me the function with the following name."
|
||
|
Without the <CODE>#'</CODE>, Lisp would treat <CODE>evenp</CODE> as the name of a
|
||
|
variable and look up the value of the variable, not the function.</P><P>You can also pass <CODE><B>REMOVE-IF-NOT</B></CODE> an anonymous function. For
|
||
|
instance, if <CODE><B>EVENP</B></CODE> didn't exist, you could write the previous
|
||
|
expression as the following: </P><PRE>CL-USER> (remove-if-not #'(lambda (x) (= 0 (mod x 2))) '(1 2 3 4 5 6 7 8 9 10))
|
||
|
(2 4 6 8 10)</PRE><P>In this case, the predicate is this anonymous function:</P><PRE>(lambda (x) (= 0 (mod x 2)))</PRE><P>which checks that its argument is equal to 0 modulus 2 (in other
|
||
|
words, is even). If you wanted to extract only the odd numbers using
|
||
|
an anonymous function, you'd write this:</P><PRE>CL-USER> (remove-if-not #'(lambda (x) (= 1 (mod x 2))) '(1 2 3 4 5 6 7 8 9 10))
|
||
|
(1 3 5 7 9)</PRE><P>Note that <CODE>lambda</CODE> isn't the name of the function--it's the
|
||
|
indicator you're defining an anonymous function.<SUP>5</SUP> Other than the lack of a name, however, a <CODE><B>LAMBDA</B></CODE>
|
||
|
expression looks a lot like a <CODE><B>DEFUN</B></CODE>: the word <CODE>lambda</CODE> is
|
||
|
followed by a parameter list, which is followed by the body of the
|
||
|
function. </P><P>To select all the Dixie Chicks' albums in the database using
|
||
|
<CODE><B>REMOVE-IF-NOT</B></CODE>, you need a function that returns true when the
|
||
|
artist field of a record is <CODE>"Dixie Chicks"</CODE>. Remember that we
|
||
|
chose the plist representation for the database records because the
|
||
|
function <CODE><B>GETF</B></CODE> can extract named fields from a plist. So assuming
|
||
|
<CODE>cd</CODE> is the name of a variable holding a single database record,
|
||
|
you can use the expression <CODE>(getf cd :artist)</CODE> to extract the
|
||
|
name of the artist. The function <CODE><B>EQUAL</B></CODE>, when given string
|
||
|
arguments, compares them character by character. So <CODE>(equal
|
||
|
(getf cd :artist) "Dixie Chicks")</CODE> will test whether the artist field
|
||
|
of a given CD is equal to <CODE>"Dixie Chicks"</CODE>. All you need to do
|
||
|
is wrap that expression in a <CODE><B>LAMBDA</B></CODE> form to make an anonymous
|
||
|
function and pass it to <CODE><B>REMOVE-IF-NOT</B></CODE>. </P><PRE>CL-USER> (remove-if-not
|
||
|
#'(lambda (cd) (equal (getf cd :artist) "Dixie Chicks")) *db*)
|
||
|
((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T)
|
||
|
(:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T))</PRE><P>Now suppose you want to wrap that whole expression in a function that
|
||
|
takes the name of the artist as an argument. You can write that like
|
||
|
this:</P><PRE>(defun select-by-artist (artist)
|
||
|
(remove-if-not
|
||
|
#'(lambda (cd) (equal (getf cd :artist) artist))
|
||
|
*db*))</PRE><P>Note how the anonymous function, which contains code that won't run
|
||
|
until it's invoked in <CODE><B>REMOVE-IF-NOT</B></CODE>, can nonetheless refer to
|
||
|
the variable <CODE>artist</CODE>. In this case the anonymous function
|
||
|
doesn't just save you from having to write a regular function--it
|
||
|
lets you write a function that derives part of its meaning--the value
|
||
|
of <CODE>artist</CODE>--from the context in which it's embedded. </P><P>So that's <CODE>select-by-artist</CODE>. However, selecting by artist is
|
||
|
only one of the kinds of queries you might like to support. You
|
||
|
<I>could</I> write several more functions, such as
|
||
|
<CODE>select-by-title</CODE>, <CODE>select-by-rating</CODE>,
|
||
|
<CODE>select-by-title-and-artist</CODE>, and so on. But they'd all be about
|
||
|
the same except for the contents of the anonymous function. You can
|
||
|
instead make a more general <CODE>select</CODE> function that takes a
|
||
|
function as an argument.</P><PRE>(defun select (selector-fn)
|
||
|
(remove-if-not selector-fn *db*))</PRE><P>So what happened to the <CODE>#'</CODE>? Well, in this case you don't want
|
||
|
<CODE><B>REMOVE-IF-NOT</B></CODE> to use the function named <CODE>selector-fn</CODE>. You
|
||
|
want it to use the anonymous function that was passed as an argument
|
||
|
to <CODE>select</CODE> in the <I>variable</I> <CODE>selector-fn</CODE>. Though, the
|
||
|
<CODE>#'</CODE> comes back in the <I>call</I> to <CODE>select</CODE>.</P><PRE>CL-USER> (select #'(lambda (cd) (equal (getf cd :artist) "Dixie Chicks")))
|
||
|
((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T)
|
||
|
(:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T))</PRE><P>But that's really quite gross-looking. Luckily, you can wrap up the
|
||
|
creation of the anonymous function. </P><PRE>(defun artist-selector (artist)
|
||
|
#'(lambda (cd) (equal (getf cd :artist) artist)))</PRE><P>This is a function that returns a function and one that references a
|
||
|
variable that--it seems--won't exist after <CODE>artist-selector</CODE>
|
||
|
returns.<SUP>6</SUP> It may seem odd now, but it actually works just the way
|
||
|
you'd want--if you call <CODE>artist-selector</CODE> with an argument of
|
||
|
<CODE>"Dixie Chicks"</CODE>, you get an anonymous function that matches CDs
|
||
|
whose <CODE>:artist</CODE> field is <CODE>"Dixie Chicks"</CODE>, and if you call
|
||
|
it with <CODE>"Lyle Lovett"</CODE>, you get a different function that will
|
||
|
match against an <CODE>:artist</CODE> field of <CODE>"Lyle Lovett"</CODE>. So now
|
||
|
you can rewrite the call to <CODE>select</CODE> like this:</P><PRE>CL-USER> (select (artist-selector "Dixie Chicks"))
|
||
|
((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T)
|
||
|
(:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T))</PRE><P>Now you just need some more functions to generate selectors. But just
|
||
|
as you don't want to have to write <CODE>select-by-title</CODE>,
|
||
|
<CODE>select-by-rating</CODE>, and so on, because they would all be quite
|
||
|
similar, you're not going to want to write a bunch of nearly
|
||
|
identical selector-function generators, one for each field. Why not
|
||
|
write one general-purpose selector-function generator, a function
|
||
|
that, depending on what arguments you pass it, will generate a
|
||
|
selector function for different fields or maybe even a combination of
|
||
|
fields? You can write such a function, but first you need a crash
|
||
|
course in a feature called <I>keyword parameters</I>.</P><P>In the functions you've written so far, you've specified a simple
|
||
|
list of parameters, which are bound to the corresponding arguments in
|
||
|
the call to the function. For instance, the following function:</P><PRE>(defun foo (a b c) (list a b c))</PRE><P>has three parameters, <CODE>a</CODE>, <CODE>b</CODE>, and <CODE>c</CODE>, and must be
|
||
|
called with three arguments. But sometimes you may want to write a
|
||
|
function that can be called with varying numbers of arguments.
|
||
|
Keyword parameters are one way to achieve this. A version of
|
||
|
<CODE>foo</CODE> that uses keyword parameters might look like this: </P><PRE>(defun foo (&key a b c) (list a b c))</PRE><P>The only difference is the <CODE>&key</CODE> at the beginning of the
|
||
|
argument list. However, the calls to this new <CODE>foo</CODE> will look
|
||
|
quite different. These are all legal calls with the result to the
|
||
|
right of the ==>:</P><PRE>(foo :a 1 :b 2 :c 3) ==> (1 2 3)
|
||
|
(foo :c 3 :b 2 :a 1) ==> (1 2 3)
|
||
|
(foo :a 1 :c 3) ==> (1 NIL 3)
|
||
|
(foo) ==> (NIL NIL NIL)</PRE><P>As these examples show, the value of the variables <CODE>a</CODE>, <CODE>b</CODE>,
|
||
|
and <CODE>c</CODE> are bound to the values that follow the corresponding
|
||
|
keyword. And if a particular keyword isn't present in the call, the
|
||
|
corresponding variable is set to <CODE><B>NIL</B></CODE>. I'm glossing over a bunch
|
||
|
of details of how keyword parameters are specified and how they relate
|
||
|
to other kinds of parameters, but you need to know one more detail.</P><P>Normally if a function is called with no argument for a particular
|
||
|
keyword parameter, the parameter will have the value <CODE><B>NIL</B></CODE>.
|
||
|
However, sometimes you'll want to be able to distinguish between a
|
||
|
<CODE><B>NIL</B></CODE> that was explicitly passed as the argument to a keyword
|
||
|
parameter and the default value <CODE><B>NIL</B></CODE>. To allow this, when you
|
||
|
specify a keyword parameter you can replace the simple name with a
|
||
|
list consisting of the name of the parameter, a default value, and
|
||
|
another parameter name, called a <I>supplied-p</I> parameter. The
|
||
|
supplied-p parameter will be set to true or false depending on
|
||
|
whether an argument was actually passed for that keyword parameter in
|
||
|
a particular call to the function. Here's a version of <CODE>foo</CODE>
|
||
|
that uses this feature:</P><PRE>(defun foo (&key a (b 20) (c 30 c-p)) (list a b c c-p))</PRE><P>Now the same calls from earlier yield these results:</P><PRE>(foo :a 1 :b 2 :c 3) ==> (1 2 3 T)
|
||
|
(foo :c 3 :b 2 :a 1) ==> (1 2 3 T)
|
||
|
(foo :a 1 :c 3) ==> (1 20 3 T)
|
||
|
(foo) ==> (NIL 20 30 NIL)</PRE><P>The general selector-function generator, which you can call
|
||
|
<CODE>where</CODE> for reasons that will soon become apparent if you're
|
||
|
familiar with SQL databases, is a function that takes four keyword
|
||
|
parameters corresponding to the fields in our CD records and
|
||
|
generates a selector function that selects any CDs that match all the
|
||
|
values given to <CODE>where</CODE>. For instance, it will let you say
|
||
|
things like this: </P><PRE>(select (where :artist "Dixie Chicks"))</PRE><P>or this:</P><PRE>(select (where :rating 10 :ripped nil))</PRE><P>The function looks like this:</P><PRE>(defun where (&key title artist rating (ripped nil ripped-p))
|
||
|
#'(lambda (cd)
|
||
|
(and
|
||
|
(if title (equal (getf cd :title) title) t)
|
||
|
(if artist (equal (getf cd :artist) artist) t)
|
||
|
(if rating (equal (getf cd :rating) rating) t)
|
||
|
(if ripped-p (equal (getf cd :ripped) ripped) t))))</PRE><P>This function returns an anonymous function that returns the logical
|
||
|
<CODE>AND</CODE> of one clause per field in our CD records. Each clause
|
||
|
checks if the appropriate argument was passed in and then either
|
||
|
compares it to the value in the corresponding field in the CD record
|
||
|
or returns <CODE>t</CODE>, Lisp's version of truth, if the parameter wasn't
|
||
|
passed in. Thus, the selector function will return <CODE>t</CODE> only for
|
||
|
CDs that match all the arguments passed to <CODE>where</CODE>.<SUP>7</SUP> Note that you need to use a three-item list to specify
|
||
|
the keyword parameter <CODE>ripped</CODE> because you need to know whether
|
||
|
the caller actually passed <CODE>:ripped nil</CODE>, meaning, "Select CDs
|
||
|
whose ripped field is nil," or whether they left out <CODE>:ripped</CODE>
|
||
|
altogether, meaning "I don't care what the value of the ripped field
|
||
|
is." </P><A NAME="updating-existing-records--another-use-for-where"><H2>Updating Existing Records--Another Use for WHERE</H2></A><P>Now that you've got nice generalized <CODE>select</CODE> and <CODE>where</CODE>
|
||
|
functions, you're in a good position to write the next feature that
|
||
|
every database needs--a way to update particular records. In SQL the
|
||
|
<CODE>update</CODE> command is used to update a set of records matching a
|
||
|
particular <CODE>where</CODE> clause. That seems like a good model,
|
||
|
especially since you've already got a <CODE>where</CODE>-clause generator.
|
||
|
In fact, the <CODE>update</CODE> function is mostly just the application of
|
||
|
a few ideas you've already seen: using a passed-in selector function
|
||
|
to choose the records to update and using keyword arguments to
|
||
|
specify the values to change. The main new bit is the use of a
|
||
|
function <CODE><B>MAPCAR</B></CODE> that maps over a list, <CODE>*db*</CODE> in this case,
|
||
|
and returns a new list containing the results of calling a function
|
||
|
on each item in the original list.</P><PRE>(defun update (selector-fn &key title artist rating (ripped nil ripped-p))
|
||
|
(setf *db*
|
||
|
(mapcar
|
||
|
#'(lambda (row)
|
||
|
(when (funcall selector-fn row)
|
||
|
(if title (setf (getf row :title) title))
|
||
|
(if artist (setf (getf row :artist) artist))
|
||
|
(if rating (setf (getf row :rating) rating))
|
||
|
(if ripped-p (setf (getf row :ripped) ripped)))
|
||
|
row) *db*)))</PRE><P>One other new bit here is the use of <CODE><B>SETF</B></CODE> on a complex form such
|
||
|
as <CODE>(getf row :title)</CODE>. I'll discuss <CODE><B>SETF</B></CODE> in greater detail
|
||
|
in Chapter 6, but for now you just need to know that it's a general
|
||
|
assignment operator that can be used to assign lots of "places" other
|
||
|
than just variables. (It's a coincidence that <CODE><B>SETF</B></CODE> and <CODE><B>GETF</B></CODE>
|
||
|
have such similar names--they don't have any special relationship.)
|
||
|
For now it's enough to know that after <CODE>(setf (getf row :title)
|
||
|
title)</CODE>, the plist referenced by row will have the value of the
|
||
|
variable <CODE>title</CODE> following the property name <CODE>:title</CODE>. With
|
||
|
this <CODE>update</CODE> function if you decide that you <I>really</I> dig the
|
||
|
Dixie Chicks and that all their albums should go to 11, you can
|
||
|
evaluate the following form: </P><PRE>CL-USER> (update (where :artist "Dixie Chicks") :rating 11)
|
||
|
NIL</PRE><P>And it is so.</P><PRE>CL-USER> (select (where :artist "Dixie Chicks"))
|
||
|
((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 11 :RIPPED T)
|
||
|
(:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 11 :RIPPED T))</PRE><P>You can even more easily add a function to delete rows from the
|
||
|
database.</P><PRE>(defun delete-rows (selector-fn)
|
||
|
(setf *db* (remove-if selector-fn *db*)))</PRE><P>The function <CODE><B>REMOVE-IF</B></CODE> is the complement of <CODE><B>REMOVE-IF-NOT</B></CODE>;
|
||
|
it returns a list with all the elements that do match the predicate
|
||
|
removed. Like <CODE><B>REMOVE-IF-NOT</B></CODE>, it doesn't actually affect the list
|
||
|
it's passed but by saving the result back into <CODE>*db*</CODE>,
|
||
|
<CODE>delete-rows</CODE><SUP>8</SUP> actually changes the contents of the
|
||
|
database.<SUP>9</SUP></P><A NAME="removing-duplication-and-winning-big"><H2>Removing Duplication and Winning Big</H2></A><P>So far <I>all</I> the database code supporting insert,
|
||
|
select, update, and delete, not to mention a command-line user
|
||
|
interface for adding new records and dumping out the contents, is
|
||
|
just a little more than 50 lines. Total.<SUP>10</SUP></P><P>Yet there's still some annoying code duplication. And it turns out you
|
||
|
can remove the duplication and make the code more flexible at the same
|
||
|
time. The duplication I'm thinking of is in the where function. The
|
||
|
body of the <CODE>where</CODE> function is a bunch of clauses like this, one
|
||
|
per field:</P><PRE>(if title (equal (getf cd :title) title) t)</PRE><P>Right now it's not so bad, but like all code duplication it has the
|
||
|
same cost: if you want to change how it works, you have to change
|
||
|
multiple copies. And if you change the fields in a CD, you'll have to
|
||
|
add or remove clauses to <CODE>where</CODE>. And <CODE>update</CODE> suffers from
|
||
|
the same kind of duplication. It's doubly annoying since the whole
|
||
|
point of the <CODE>where</CODE> function is to dynamically generate a bit
|
||
|
of code that checks the values you care about; why should it have to
|
||
|
do work at runtime checking whether <CODE>title</CODE> was even passed in?</P><P>Imagine that you were trying to optimize this code and discovered
|
||
|
that it was spending too much time checking whether <CODE>title</CODE> and
|
||
|
the rest of the keyword parameters to <CODE>where</CODE> were even
|
||
|
set?<SUP>11</SUP> If you really wanted to
|
||
|
remove all those runtime checks, you could go through a program and
|
||
|
find all the places you call <CODE>where</CODE> and look at exactly what
|
||
|
arguments you're passing. Then you could replace each call to
|
||
|
<CODE>where</CODE> with an anonymous function that does only the
|
||
|
computation necessary. For instance, if you found this snippet of
|
||
|
code:</P><PRE>(select (where :title "Give Us a Break" :ripped t))</PRE><P>you could change it to this:</P><PRE>(select
|
||
|
#'(lambda (cd)
|
||
|
(and (equal (getf cd :title) "Give Us a Break")
|
||
|
(equal (getf cd :ripped) t))))</PRE><P>Note that the anonymous function is different from the one that
|
||
|
<CODE>where</CODE> would have returned; you're not trying to save the call
|
||
|
to <CODE>where</CODE> but rather to provide a more efficient selector
|
||
|
function. This anonymous function has clauses only for the fields
|
||
|
that you actually care about at this call site, so it doesn't do any
|
||
|
extra work the way a function returned by <CODE>where</CODE> might. </P><P>You can probably imagine going through all your source code and
|
||
|
fixing up all the calls to <CODE>where</CODE> in this way. But you can
|
||
|
probably also imagine that it would be a huge pain. If there were
|
||
|
enough of them, and it was important enough, it might even be
|
||
|
worthwhile to write some kind of preprocessor that converts
|
||
|
<CODE>where</CODE> calls to the code you'd write by hand. </P><P>The Lisp feature that makes this trivially easy is its macro system.
|
||
|
I can't emphasize enough that the Common Lisp macro shares
|
||
|
essentially nothing but the name with the text-based macros found in
|
||
|
C and C++. Where the C pre-processor operates by textual substitution
|
||
|
and understands almost nothing of the structure of C and C++, a Lisp
|
||
|
macro is essentially a code generator that gets run for you
|
||
|
automatically by the compiler.<SUP>12</SUP> When
|
||
|
a Lisp expression contains a call to a macro, instead of evaluating
|
||
|
the arguments and passing them to the function, the Lisp compiler
|
||
|
passes the arguments, unevaluated, to the macro code, which returns a
|
||
|
new Lisp expression that is then evaluated in place of the original
|
||
|
macro call.</P><P>I'll start with a simple, and silly, example and then show how you
|
||
|
can replace the <CODE>where</CODE> function with a <CODE>where</CODE> macro.
|
||
|
Before I can write this example macro, I need to quickly introduce
|
||
|
one new function: <CODE>REVERSE</CODE> takes a list as an argument and
|
||
|
returns a new list that is its reverse. So <CODE>(reverse '(1 2 3))</CODE>
|
||
|
evaluates to <CODE>(3 2 1)</CODE>. Now let's create a macro. </P><PRE>(defmacro backwards (expr) (reverse expr))</PRE><P>The main syntactic difference between a function and a macro is that
|
||
|
you define a macro with <CODE><B>DEFMACRO</B></CODE> instead of <CODE><B>DEFUN</B></CODE>. After
|
||
|
that a macro definition consists of a name, just like a function, a
|
||
|
parameter list, and a body of expressions, both also like a function.
|
||
|
However, a macro has a totally different effect. You can use this
|
||
|
macro as follows:</P><PRE>CL-USER> (backwards ("hello, world" t format))
|
||
|
hello, world
|
||
|
NIL</PRE><P>How did that work? When the REPL started to evaluate the
|
||
|
<CODE>backwards</CODE> expression, it recognized that <CODE>backwards</CODE> is
|
||
|
the name of a macro. So it left the expression <CODE>("hello, world"
|
||
|
t format)</CODE> unevaluated, which is good because it isn't a legal Lisp
|
||
|
form. It then passed that list to the <CODE>backwards</CODE> code. The code
|
||
|
in <CODE>backwards</CODE> passed the list to <CODE><B>REVERSE</B></CODE>, which returned
|
||
|
the list <CODE>(format t "hello, world")</CODE>. <CODE>backwards</CODE> then
|
||
|
passed that value back out to the REPL, which then evaluated it in
|
||
|
place of the original expression.</P><P>The <CODE>backwards</CODE> macro thus defines a new language that's a lot
|
||
|
like Lisp--just backward--that you can drop into anytime simply by
|
||
|
wrapping a backward Lisp expression in a call to the <CODE>backwards</CODE>
|
||
|
macro. And, in a compiled Lisp program, that new language is just as
|
||
|
efficient as normal Lisp because all the macro code--the code that
|
||
|
generates the new expression--runs at compile time. In other words,
|
||
|
the compiler will generate exactly the same code whether you write
|
||
|
<CODE>(backwards ("hello, world" t format))</CODE> or <CODE>(format t
|
||
|
"hello, world")</CODE>.</P><P>So how does that help with the code duplication in <CODE>where</CODE>?
|
||
|
Well, you can write a macro that generates exactly the code you need
|
||
|
for each particular call to <CODE>where</CODE>. Again, the best approach is
|
||
|
to build our code bottom up. In the hand-optimized selector function,
|
||
|
you had an expression of the following form for each actual field
|
||
|
referred to in the original call to <CODE>where</CODE>:</P><PRE>(equal (getf cd <I>field</I>) <I>value</I>)</PRE><P>So let's write a function that, given the name of a field and a
|
||
|
value, returns such an expression. Since an expression is just a
|
||
|
list, you might think you could write something like this:</P><PRE>(defun make-comparison-expr (field value) ; wrong
|
||
|
(list equal (list getf cd field) value))</PRE><P>However, there's one trick here: as you know, when Lisp sees a simple
|
||
|
name such as <CODE>field</CODE> or <CODE>value</CODE> other than as the first
|
||
|
element of a list, it assumes it's the name of a variable and looks
|
||
|
up its value. That's fine for <CODE>field</CODE> and <CODE>value</CODE>; it's
|
||
|
exactly what you want. But it will treat <CODE>equal</CODE>, <CODE>getf</CODE>,
|
||
|
and <CODE>cd</CODE> the same way, which <I>isn't</I> what you want. However,
|
||
|
you also know how to stop Lisp from evaluating a form: stick a single
|
||
|
forward quote (<CODE>'</CODE>) in front of it. So if you write
|
||
|
<CODE>make-comparison-expr</CODE> like this, it will do what you want: </P><PRE>(defun make-comparison-expr (field value)
|
||
|
(list 'equal (list 'getf 'cd field) value))</PRE><P>You can test it out in the REPL.</P><PRE>CL-USER> (make-comparison-expr :rating 10)
|
||
|
(EQUAL (GETF CD :RATING) 10)
|
||
|
CL-USER> (make-comparison-expr :title "Give Us a Break")
|
||
|
(EQUAL (GETF CD :TITLE) "Give Us a Break")</PRE><P>It turns out that there's an even better way to do it. What you'd
|
||
|
really like is a way to write an expression that's mostly not
|
||
|
evaluated and then have some way to pick out a few expressions that
|
||
|
you <I>do</I> want evaluated. And, of course, there's just such a
|
||
|
mechanism. A back quote (<CODE>`</CODE>) before an expression stops
|
||
|
evaluation just like a forward quote.</P><PRE>CL-USER> `(1 2 3)
|
||
|
(1 2 3)
|
||
|
CL-USER> '(1 2 3)
|
||
|
(1 2 3)</PRE><P>However, in a back-quoted expression, any subexpression that's
|
||
|
preceded by a comma is evaluated. Notice the effect of the comma in
|
||
|
the second expression:</P><PRE>`(1 2 (+ 1 2)) ==> (1 2 (+ 1 2))
|
||
|
`(1 2 ,(+ 1 2)) ==> (1 2 3)</PRE><P>Using a back quote, you can write <CODE>make-comparison-expr</CODE> like
|
||
|
this: </P><PRE>(defun make-comparison-expr (field value)
|
||
|
`(equal (getf cd ,field) ,value))</PRE><P>Now if you look back to the hand-optimized selector function, you can
|
||
|
see that the body of the function consisted of one comparison
|
||
|
expression per field/value pair, all wrapped in an <CODE><B>AND</B></CODE>
|
||
|
expression. Assume for the moment that you'll arrange for the
|
||
|
arguments to the <CODE>where</CODE> macro to be passed as a single list.
|
||
|
You'll need a function that can take the elements of such a list
|
||
|
pairwise and collect the results of calling
|
||
|
<CODE>make-comparison-expr</CODE> on each pair. To implement that function,
|
||
|
you can dip into the bag of advanced Lisp tricks and pull out the
|
||
|
mighty and powerful <CODE>LOOP</CODE> macro.</P><PRE>(defun make-comparisons-list (fields)
|
||
|
(loop while fields
|
||
|
collecting (make-comparison-expr (pop fields) (pop fields))))</PRE><P>A full discussion of <CODE>LOOP</CODE> will have to wait until Chapter 22;
|
||
|
for now just note that this <CODE>LOOP</CODE> expression does exactly what
|
||
|
you need: it loops while there are elements left in the <CODE>fields</CODE>
|
||
|
list, popping off two at a time, passing them to
|
||
|
<CODE>make-comparison-expr</CODE>, and collecting the results to be
|
||
|
returned at the end of the loop. The <CODE><B>POP</B></CODE> macro performs the
|
||
|
inverse operation of the <CODE>PUSH</CODE> macro you used to add records to
|
||
|
<CODE>*db*</CODE>.</P><P>Now you just need to wrap up the list returned by
|
||
|
<CODE>make-comparison-list</CODE> in an <CODE>AND</CODE> and an anonymous
|
||
|
function, which you can do in the <CODE>where</CODE> macro itself. Using a
|
||
|
back quote to make a template that you fill in by interpolating the
|
||
|
value of <CODE>make-comparisons-list</CODE>, it's trivial.</P><PRE>(defmacro where (&rest clauses)
|
||
|
`#'(lambda (cd) (and ,@(make-comparisons-list clauses))))</PRE><P>This macro uses a variant of <CODE>,</CODE> (namely, the <CODE>,@</CODE>) before
|
||
|
the call to <CODE>make-comparisons-list</CODE>. The <CODE>,@</CODE> "splices" the
|
||
|
value of the following expression--which must evaluate to a
|
||
|
list--into the enclosing list. You can see the difference between
|
||
|
<CODE>,</CODE> and <CODE>,@</CODE> in the following two expressions: </P><PRE>`(and ,(list 1 2 3)) ==> (AND (1 2 3))
|
||
|
`(and ,@(list 1 2 3)) ==> (AND 1 2 3)</PRE><P>You can also use <CODE>,@</CODE> to splice into the middle of a list.</P><PRE>`(and ,@(list 1 2 3) 4) ==> (AND 1 2 3 4)</PRE><P>The other important feature of the <CODE>where</CODE> macro is the use of
|
||
|
<CODE>&rest</CODE> in the argument list. Like <CODE>&key</CODE>, <CODE>&rest</CODE>
|
||
|
modifies the way arguments are parsed. With a <CODE>&rest</CODE> in its
|
||
|
parameter list, a function or macro can take an arbitrary number of
|
||
|
arguments, which are collected into a single list that becomes the
|
||
|
value of the variable whose name follows the <CODE>&rest</CODE>. So if you
|
||
|
call <CODE>where</CODE> like this:</P><PRE>(where :title "Give Us a Break" :ripped t)</PRE><P>the variable <CODE>clauses</CODE> will contain the list.</P><PRE>(:title "Give Us a Break" :ripped t)</PRE><P>This list is passed to <CODE>make-comparisons-list</CODE>, which returns a
|
||
|
list of comparison expressions. You can see exactly what code a call
|
||
|
to <CODE>where</CODE> will generate using the function <CODE><B>MACROEXPAND-1</B></CODE>.
|
||
|
If you pass <CODE><B>MACROEXPAND-1</B></CODE>, a form representing a macro call, it
|
||
|
will call the macro code with appropriate arguments and return the
|
||
|
expansion. So you can check out the previous <CODE>where</CODE> call like
|
||
|
this: </P><PRE>CL-USER> (macroexpand-1 '(where :title "Give Us a Break" :ripped t))
|
||
|
#'(LAMBDA (CD)
|
||
|
(AND (EQUAL (GETF CD :TITLE) "Give Us a Break")
|
||
|
(EQUAL (GETF CD :RIPPED) T)))
|
||
|
T</PRE><P>Looks good. Let's try it for real.</P><PRE>CL-USER> (select (where :title "Give Us a Break" :ripped t))
|
||
|
((:TITLE "Give Us a Break" :ARTIST "Limpopo" :RATING 10 :RIPPED T))</PRE><P>It works. And the <CODE>where</CODE> macro with its two helper functions is
|
||
|
actually one line shorter than the old <CODE>where</CODE> function. And
|
||
|
it's more general in that it's no longer tied to the specific fields
|
||
|
in our CD records. </P><A NAME="wrapping-up"><H2>Wrapping Up</H2></A><P>Now, an interesting thing has happened. You removed duplication and
|
||
|
made the code more efficient <I>and</I> more general at the same time.
|
||
|
That's often the way it goes with a well-chosen macro. This makes
|
||
|
sense because a macro is just another mechanism for creating
|
||
|
abstractions--abstraction at the syntactic level, and abstractions are
|
||
|
by definition more concise ways of expressing underlying generalities.
|
||
|
Now the only code in the mini-database that's specific to CDs and the
|
||
|
fields in them is in the <CODE>make-cd</CODE>, <CODE>prompt-for-cd</CODE>, and
|
||
|
<CODE>add-cd</CODE> functions. In fact, our new <CODE>where</CODE> macro would
|
||
|
work with any plist-based database.</P><P>However, this is still far from being a complete database. You can
|
||
|
probably think of plenty of features to add, such as supporting
|
||
|
multiple tables or more elaborate queries. In Chapter 27 we'll build
|
||
|
an MP3 database that incorporates some of those features.</P><P>The point of this chapter was to give you a quick introduction to
|
||
|
just a handful of Lisp's features and show how they're used to write
|
||
|
code that's a bit more interesting than "hello, world." In the next
|
||
|
chapter we'll begin a more systematic overview of Lisp.
|
||
|
</P><HR/><DIV CLASS="notes"><P><SUP>1</SUP>Before I
|
||
|
proceed, however, it's crucially important that you forget anything
|
||
|
you may know about #define-style "macros" as implemented in the C
|
||
|
pre-processor. Lisp macros are a totally different beast.</P><P><SUP>2</SUP>Using a global variable also
|
||
|
has some drawbacks--for instance, you can have only one database at a
|
||
|
time. In Chapter 27, with more of the language under your belt,
|
||
|
you'll be ready to build a more flexible database. You'll also see,
|
||
|
in Chapter 6, how even using a global variable is more flexible in
|
||
|
Common Lisp than it may be in other languages.</P><P><SUP>3</SUP>One of the
|
||
|
coolest <CODE><B>FORMAT</B></CODE> directives is the <CODE>~R</CODE> directive. Ever want
|
||
|
to know how to say a really big number in English words? Lisp knows.
|
||
|
Evaluate this:</P><PRE>(format nil "~r" 1606938044258990275541962092)</PRE><P>and you should get back (wrapped for legibility):</P><BLOCKQUOTE>"one octillion six hundred six septillion nine hundred thirty-eight
|
||
|
sextillion forty-four quintillion two hundred fifty-eight
|
||
|
quadrillion nine hundred ninety trillion two hundred seventy-five
|
||
|
billion five hundred forty-one million nine hundred sixty-two
|
||
|
thousand ninety-two"</BLOCKQUOTE><P><SUP>4</SUP>Windows
|
||
|
actually understands forward slashes in filenames even though it
|
||
|
normally uses a backslash as the directory separator. This is
|
||
|
convenient since otherwise you have to write double backslashes
|
||
|
because backslash is the escape character in Lisp strings.</P><P><SUP>5</SUP>The word lambda
|
||
|
is used in Lisp because of an early connection to the lambda
|
||
|
calculus, a mathematical formalism invented for studying mathematical
|
||
|
functions.</P><P><SUP>6</SUP>The technical term for a function that references a
|
||
|
variable in its enclosing scope is a closure because the function
|
||
|
"closes over" the variable. I'll discuss closures in more detail in
|
||
|
Chapter 6.</P><P><SUP>7</SUP>Note
|
||
|
that in Lisp, an IF form, like everything else, is an expression that
|
||
|
returns a value. It's actually more like the ternary operator
|
||
|
(<CODE>?:</CODE>) in Perl, Java, and C in that this is legal in those
|
||
|
languages:</P><PRE>some_var = some_boolean ? value1 : value2;</PRE><P>while this isn't:</P><PRE>some_var = if (some_boolean) value1; else value2;</PRE><P>because in those languages, <CODE>if</CODE> is a statement, not an
|
||
|
expression.</P><P><SUP>8</SUP>You need to use the name <CODE>delete-rows</CODE>
|
||
|
rather than the more obvious <CODE>delete</CODE> because there's already a
|
||
|
function in Common Lisp called <CODE><B>DELETE</B></CODE>. The Lisp package system
|
||
|
gives you a way to deal with such naming conflicts, so you could have
|
||
|
a function named delete if you wanted. But I'm not ready to explain
|
||
|
packages just yet.</P><P><SUP>9</SUP>If you're worried that this code creates a memory
|
||
|
leak, rest assured: Lisp was the language that invented garbage
|
||
|
collection (and heap allocation for that matter). The memory used by
|
||
|
the old value of <CODE>*db*</CODE> will be automatically reclaimed,
|
||
|
assuming no one else is holding on to a reference to it, which none
|
||
|
of this code is.</P><P><SUP>10</SUP>A friend of mine was
|
||
|
once interviewing an engineer for a programming job and asked him a
|
||
|
typical interview question: how do you know when a function or method
|
||
|
is too big? Well, said the candidate, I don't like any method to be
|
||
|
bigger than my head. You mean you can't keep all the details in your
|
||
|
head? No, I mean I put my head up against my monitor, and the code
|
||
|
shouldn't be bigger than my head.</P><P><SUP>11</SUP>It's unlikely that the cost of checking whether keyword
|
||
|
parameters had been passed would be a detectible drag on performance
|
||
|
since checking whether a variable is <CODE>NIL</CODE> is going to be pretty
|
||
|
cheap. On the other hand, these functions returned by <CODE>where</CODE>
|
||
|
are going to be right in the middle of the inner loop of any
|
||
|
<CODE>select</CODE>, <CODE>update</CODE>, or <CODE>delete-rows</CODE> call, as they
|
||
|
have to be called once per entry in the database. Anyway, for
|
||
|
illustrative purposes, this will have to do.</P><P><SUP>12</SUP>Macros are also run by the
|
||
|
interpreter--however, it's easier to understand the point of macros
|
||
|
when you think about compiled code. As with everything else in this
|
||
|
chapter, I'll cover this in greater detail in future chapters.</P></DIV></BODY></HTML>
|