You can see exactly what code a call to where
will generate using the function MACROEXPAND-1
. If you pass MACROEXPAND- 1
, 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 where
call like this:
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
Looks good. Let's try it for real.
CL-USER> (select (where :title 'Give Us a Break' :ripped t))
((:TITLE 'Give Us a Break' :ARTIST 'Limpopo' :RATING 10 :RIPPED T))
It works. And the where
macro with its two helper functions is actually one line shorter than the old where
function. And it's more general in that it's no longer tied to the specific fields in our CD records.
Now, an interesting thing has happened. You removed duplication and made the code more efficient 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.
4. Syntax and Semantics
After that whirlwind tour, we'll settle down for a few chapters to take a more systematic look at the features you've used so far. I'll start with an overview of the basic elements of Lisp's syntax and semantics, which means, of course, that I must first address that burning question. . .
Lisp's syntax is quite a bit different from the syntax of languages descended from Algol. The two most immediately obvious characteristics are the extensive use of parentheses and prefix notation. For whatever reason, a lot of folks are put off by this syntax. Lisp's detractors tend to describe the syntax as 'weird' and 'annoying.' Lisp, they say, must stand for Lots of Irritating Superfluous Parentheses. Lisp folks, on the other hand, tend to consider Lisp's syntax one of its great virtues. How is it that what's so off-putting to one group is a source of delight to another?
I can't really make the complete case for Lisp's syntax until I've explained Lisp's macros a bit more thoroughly, but I can start with an historical tidbit that suggests it may be worth keeping an open mind: when John McCarthy first invented Lisp, he intended to implement a more Algol-like syntax, which he called
The project of defining M-expressions precisely and compiling them or at least translating them into S- expressions was neither finalized nor explicitly abandoned. It just receded into the indefinite future, and a new generation of programmers appeared who preferred [S-expressions] to any FORTRAN-like or ALGOL-like notation that could be devised.
In other words, the people who have actually used Lisp over the past 45 years have
Before we look at the specifics of Lisp's syntax and semantics, it's worth taking a moment to look at how they're defined and how this differs from many other languages.
In most programming languages, the language processor—whether an interpreter or a compiler—operates as a black box: you shove a sequence of characters representing the text of a program into the black box, and it— depending on whether it's an interpreter or a compiler—either executes the behaviors indicated or produces a compiled version of the program that will execute the behaviors when it's run.
Inside the black box, of course, language processors are usually divided into subsystems that are each responsible for one part of the task of translating a program text into behavior or object code. A typical division is to split the processor into three phases, each of which feeds into the next: a lexical analyzer breaks up the stream of characters into tokens and feeds them to a parser that builds a tree representing the expressions in the program, according to the language's grammar. This tree—called an
In Common Lisp things are sliced up a bit differently, with consequences for both the implementer and for how the language is defined. Instead of a single black box that goes from text to program behavior in one step, Common Lisp defines
Each black box defines one level of syntax. The reader defines how strings of characters can be translated into Lisp objects called
The evaluator then defines a syntax of Lisp