有两种方式来给Lisp添加新的操作符:函数和宏。在Lisp中,你定义的函数和内置的那些有一样的状态。如果你想要一个mapcar的变体,你可以自己定义一个,并像使用mapcar一样使用它。比如,如果你想要一个函数应用于1到10的变量后返回的一列值,你可以创建一个新的列表并将它传递给mapcar:
(mapcar fn
(do* ((x 1 (1+ x))
(result (list x) (push x result)))
((= x 10) (nreverse result))))
但是这种方法既丑陋又低效。你想定义一个新的映射函数map1-n,然后想下面这样调用它:
(map1-n fn 10)
定义函数相对来说是很直观的。宏提供了更加通用的,但是并不易懂的,定义新操作符的方法。宏是是写程序的程序。这种语句有广泛的蕴涵,探索它们是这本书的主要目的。
细心地使用宏可以得到令人惊奇的清晰和优雅的程序。这些宝石也不是免费得到的。最终宏将在这个世界最自然的东西,但是一开始要理解它会有些难度。部分原因是它们比函数更泛化,所以当写它们的时候需要想着更多的东西。但是宏难理解的主要原因是它们是外语。没有其它语言有像Lisp的宏这样的东西。这样,学习关于宏的东西就需要你忘记从其它语言中不经意间学到的概念。为什么数据结构应该是流畅且可变的,而程序不是?在Lisp中,程序就是数据,但是这种事实的蕴涵关系需要时间来深入理解。
如果需要一定的时间来适应宏,那也是非常值得的。今世是在迭代这样平常的使用,宏可以使得程序显著地变小和清晰。假设一个程序必须在某些代码上迭代a到b范围的x。内置的LIsp do 一本认为是更加泛化的情况。对于简单的迭代,它不会产生出最易读的代码:
(do ((x a (+ 1 x)))
((> x b))
(print x))
如果,假设我们能这样写:
(for (x a b)
(print x))
宏使得这样写成为可能。通过6行代码我们就能将for加入到语言中,就像它从一开始就在那里一样。随着章节的继续,写for仅仅是你可以用宏来做的事情的开始。
你没有被限制在通过函数和宏来扩展Lisp。如果你需要,你可以在Lisp上建立一整套语言,并且用这套语言写你的程序。Lisp是一个出色的编写编译器和解释器的语言,但是它
提供了另外一种定义新语言的方式,这种方式更加优雅,需要更少的工作:作为Lisp的变体定义的新语言。然后Lisp的部分可以在新语言中不经修改的出现,你只需要实现不同的部分。以这种方式实现的语言叫做嵌入式语言。
嵌入式语言是自下而上编程的自然结果。Common Lisp已经包含几个这样的语言。它们中最著名的是CLOS,在后面的章节中讨论。但是你也可以定义你自己的嵌入式语言。你可以让语言适应你的程序,可以最终看起来和Lisp很不同。