3. Practical: A Simple Database
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.
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.
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:
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.
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).
For now you can stay at the simple end of the spectrum and use a list. You can make a list with the LIST
function, which, appropriately enough, returns a list of its arguments.
CL-USER> (list 1 2 3)
(1 2 3)
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 :
), for instance, :foo
. Here's an example of a plist using the keyword symbols :a
, :b
, and :c
as property names:
CL-USER> (list :a 1 :b 2 :c 3)
(:A 1 :B 2 :C 3)
Note that you can create a property list with the same LIST
function as you use to create other lists; it's the contents that make it a plist.
The thing that makes plists a convenient way to represent the records in a database is the function GETF
, 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.
CL-USER> (getf (list :a 1 :b 2 :c 3) :a)
1
CL-USER> (getf (list :a 1 :b 2 :c 3) :c)
3
Given all that, you can easily enough write a function make-cd
that will take the four fields as arguments and return a plist representing that CD.
(defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped ripped))
The word DEFUN
tells us that this form is defining a new function. The name of the function is make-cd
. After the name comes the parameter list. This function has four parameters: title
, artist
, rating
, and ripped
. Everything after the parameter list is the body of the function. In this case the body is just one form, a call to LIST
. When make-cd
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 make-cd
like this:
CL-USER> (make-cd 'Roses' 'Kathy Mattea' 7 t)
(:TITLE 'Roses' :ARTIST 'Kathy Mattea' :RATING 7 :RIPPED T)
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, *db*
, which you can define with the DEFVAR
macro. The asterisks (*) in the name are a Lisp naming convention for global variables.[25]
(defvar *db* nil)
You can use the PUSH
macro to add items to *db*
. But it's probably a good idea to abstract things a tiny bit, so you should define a function add-record
that adds a record to the database.
(defun add-record (cd) (push cd *db*))
Now you can use add-record
and make-cd
together to add CDs to the database.
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))