Wrapping Up
Now, an interesting thing has happened. You removed duplication and made the code more efficient and 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 make-cd
, prompt-for-cd
, and add-cd
functions. In fact, our new where
macro would work with any plist-based database.
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.
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.
1Before 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.
2Using 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.
3One of the coolest **FORMAT**
directives is the ~R
directive. Ever want to know how to say a really big number in English words? Lisp knows. Evaluate this:
(format nil "~r" 1606938044258990275541962092)
and you should get back (wrapped for legibility):
“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”
4Windows 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.
5The word lambda is used in Lisp because of an early connection to the lambda calculus, a mathematical formalism invented for studying mathematical functions.
6The 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.
7Note that in Lisp, an IF form, like everything else, is an expression that returns a value. It’s actually more like the ternary operator (?:
) in Perl, Java, and C in that this is legal in those languages:
some_var = some_boolean ? value1 : value2;
while this isn’t:
some_var = if (some_boolean) value1; else value2;
because in those languages, if
is a statement, not an expression.
8You need to use the name delete-rows
rather than the more obvious delete
because there’s already a function in Common Lisp called **DELETE**
. 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.
9If 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 *db*
will be automatically reclaimed, assuming no one else is holding on to a reference to it, which none of this code is.
10A 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.
11It’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 NIL
is going to be pretty cheap. On the other hand, these functions returned by where
are going to be right in the middle of the inner loop of any select
, update
, or delete-rows
call, as they have to be called once per entry in the database. Anyway, for illustrative purposes, this will have to do.
12Macros 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.