《Let Over Lambda》免费PDF:深入Common Lisp宏与闭包
2026/8/30 6:01:10 网站建设 项目流程

《Let Over Lambda》是一本讲 Common Lisp 宏编程和元编程的经典书,作者 Doug Hoyte 在官网公开了完整内容,可以直接在线阅读,也提供 PDF 版本。很多人看到这个英文书名就先被劝退,觉得门槛太高,实际上如果你已经写过一段时间 Lisp,这本书是提升宏能力最直接、最硬核的材料。它不教你背语法,而是把宏、闭包、读取宏、代码遍历器这些平时最容易绕开的知识,用大量真实代码拆开讲。适合已经能写 Common Lisp 小项目、想深入理解宏和 DSL 的开发者;完全零基础的话,建议先补一本入门书再来。市面上讲 Common Lisp 的资料不少,但愿意把宏从原理讲到落地、还把整本书以 PDF 形式免费公开的并不多,这是它值得专门写一篇博客的原因。下面从这本书讲什么、怎么免费拿到 PDF、怎么搭环境跑代码、怎么排坑这几个方面拆开说。

1. 这本书到底讲什么,为什么书名这么怪

1.1 书名里的 let 和 lambda 组合起来就是闭包

先解释书名。Common Lisp 里lambda用来创建一个匿名函数,let用来创建局部变量绑定。把let套在lambda外层,就形成了一个能捕获外部环境的函数,也就是闭包。

(let ((counter 0)) (lambda () (incf counter)))

这段代码每次调用返回的内部函数,都会让counter加 1。变量counter没有暴露给外部,却能被函数记住并修改。这就是 "let over lambda" 字面的意思:一个let包着一个lambda,形成持久环境。作者用这个命名来点题,整本书讲的都是如何利用这种环境捕获能力、宏展开机制和代码生成来写出更灵活的程序。

很多初学者会在这里卡住,觉得闭包是函数式语言才有的概念。其实 Common Lisp 里闭包非常常用,只是多数入门教材没有把它的威力展开。这本书一开始就把闭包当成核心工具,后面所有宏技巧都建立在这个基础上。

1.2 全书主线:宏就是“代码生成函数”

这本书最核心的观点,是把宏看作一个从代码到代码的函数。普通函数接收运行时数据,返回运行时数据;宏接收的是源码结构,返回的是另一段源码结构,然后再交给编译器去编译。

比如一个最简单的宏:

(defmacro my-when (condition &body body) `(if ,condition (progn ,@body) nil))

my-when接收一个条件和若干条表达式,返回一个if表达式。你可以用macroexpand-1看它展开成什么:

(macroexpand-1 '(my-when t (print "ok") (print "done")))

展开结果是:

(IF T (PROGN (PRINT "ok") (PRINT "done")) NIL)

这个展开结果并不神秘,但它的意义很大:你可以在编译之前对代码做转换,这意味着可以设计自己的语法、实现领域专用语言,把重复的样板代码消掉。市面上与宏相关的书不少,但这本会把宏从基础写到读取宏(read macro)和代码遍历器(code walker)的深度,这是它最大的差异点。

1.3 适合谁读,不适合谁读

我见过不少读者刚看完 Practical Common Lisp 就直接冲进来,结果前两章还能跟上,到读取宏和代码遍历器就放弃了。所以先把这个边界说清楚。

情况建议
已经会 Common Lisp 基础语法,写过小工具适合,建议通读
想写 DSL、自定义语法、代码生成器非常适合
想理解宏展开和闭包底层行为很适合,尤其前 3 章
完全没写过 Lisp,只熟悉 Python/JavaScript不适合,先读入门书
只是想把这本书当手册查语法不合适,它不适合当字典

如果你是从其他语言转过来的,第一次接触 Lisp,前面提到的 Practical Common Lisp 是更好的第一本。这一本更适合作为“第二本”来读,因为它默认你已经具备基本读写能力,不会在carcdr、列表操作上花太多时间解释。

2. 免费 PDF 怎么拿,拿到后怎么读

2.1 官方渠道直接可见

这本书目前是作者在官网公开的免费版本,不需要注册,也不需要专门工具。标题里说得直白:free online to read as pdf。你可以直接打开作者的官网 letoverlambda.com,页面里就能找到整本书的在线阅读入口,PDF 本身支持在浏览器里直接查看,也可以保存到本地。

这里要强调一个原则:官方公开的免费版本,阅读时用正常浏览器和普通 PDF 阅读器就够了。网上有些来路不明的“PDF 解密”、“PDF 破解编辑器”之类的东西,完全没必要碰。官方文件没有额外限制,强行去折腾反而容易下载到捆绑软件或风险程序。

注意:这里说的免费是官方公开的免费版本,不是从第三方渠道抓取的扫描件或修改版。正规来源的文件,正常阅读即可,不需要任何破解步骤。

2.2 不同设备上的打开方式

我一般会在电脑上读 PDF 原文,同时把代码示例单独放到一个文件里跑。如果你需要在平板或手机上读,可以这样:

  • Windows / macOS 桌面浏览器:直接拖进 Chrome 或 Edge,浏览器内置 PDF 阅读器就能看,支持搜索、缩放、书签。
  • 手机 / 平板:用系统自带的文件 App 预览,或安装一款正规的 PDF 阅读器,不一定需要付费版本。
  • 如果想在阅读器右侧显示缩略图或目录,一般在查看菜单里找“显示页面缩略图”或“侧边栏”,和“pdf 怎么在文件右侧预览”是同一类操作,具体入口看软件版本。

有些网盘或邮件附件打开 PDF 时会先提示“正在准备以阅读”,通常是文件还没有完全加载完,等一会儿或点一下重试就正常了。如果一直卡住,优先检查文件是否完整、路径是否含中文或特殊字符,而不是急着换工具。

2.3 想打印或摘录,先看需求再选方案

读技术书最常见的需求是把某几页打出来,或把一段代码复制到本地。我的建议是:

  • 只看部分章节:浏览器里直接选中,复制文字即可。
  • 想打印:用浏览器打印功能,选择需要的页码范围,输出到打印机或另存为 PDF。
  • 想整本转 Word:多数场景用不到。技术书里代码排版特殊,转过去格式很容易乱,反而影响阅读。
  • 想离线提取文字做笔记:可以使用正规离线文本提取工具,优先用 PDF 阅读器自带的“选择文本”功能。如果发现某个 PDF 无法选中文字,先确认文件来源是否正规。

如果只是做学习笔记,我建议不要整本转格式。把 PDF 当作阅读源,代码单独用编辑器打开,笔记写成 Markdown,比什么都方便。

3. 读这本书之前,先搭一个能跑 Common Lisp 的环境

3.1 为什么一定要跑代码

这本书不是让你读懂的,是让你跑懂的。宏的行为非常依赖展开结果,只看文字描述很容易误判。最可靠的方法是每看到一个宏定义,就自己敲一遍,然后用macroexpand-1看它展开了什么。

如果你只是把 PDF 存在硬盘里,不打开 REPL,那这本书的价值最多发挥两成。宏相关的知识必须动手验证,否则下一章的技巧建立在上面的错误理解上,越看越迷糊。

3.2 最小环境:SBCL 加一个 REPL

我推荐先装 SBCL,它是目前最常用、维护活跃的 Common Lisp 实现之一。安装方式取决于你的系统:

# Linux(Debian/Ubuntu 系) sudo apt update sudo apt install sbcl # macOS,已安装 Homebrew brew install sbcl

Windows 用户去 SBCL 官网下载安装包,安装后打开命令行输入sbcl --version能看到版本号,就说明装好了。如果只是想快速验证代码,也可以在浏览器里找在线 REPL,但本地 SBCL 更稳,后面装 Quicklisp 也方便。

注意:书里的代码大多基于 Common Lisp 标准,但不同实现和不同版本会有细微差异。碰到报错时,先确认 SBCL 版本和你敲的代码是否完全一致,不要一上来就怀疑书里的写法有问题。

3.3 最小验证:跑通第一段代码

新建一个hello.lisp

(format t "Hello, Let Over Lambda~%")

运行:

sbcl --script hello.lisp

看到输出了Hello, Let Over Lambda,环境就通了。然后进入 REPL:

sbcl

在提示符下输入:

(let ((counter 0)) (lambda () (incf counter)))

incflet的用法在 REPL 里跑一遍,比看书上一个小时都直观。编辑器方面,Emacs 加 Slime 是常用的组合,配置起来要花一点时间;如果你暂时不想碰 Emacs,用 VSCode 加合适的 Common Lisp 扩展,或者干脆在终端里写代码,也能完成这本书的大多数练习。

3.4 再加一个宏展开工具

宏调试最常用的函数就是macroexpand-1。它只展开最外层一层,不会递归展开到底,非常适合看单个宏的转换逻辑。你可以把书里任何一个宏定义抄进 REPL,然后对它传入示例表达式,看返回的展开结果,再对照书里的解释,这样学得最扎实。

如果你的环境装了 Quicklisp,后面需要引入第三方库时会省很多事。但前几章基本用不到,可以先不装,真正需要时再补。

4. 推荐阅读顺序:别从第一章硬啃到最后

4.1 先把 lambda 和闭包补熟

前几章讲的 lambda 推导和闭包,是全书的基石。不要把这一部分当成复习跳过。建议做三件事:

  • 手写几个创建闭包的函数,比如计数器、延迟求值、函数组合。
  • funcallapply分别调用返回的函数,理解调用方式和普通函数调用的区别。
  • 观察闭包捕获变量后,变量生命周期如何延长。

这些动作做下来,再看后面的宏章节会顺很多。

4.2 把宏当成参数化的代码模板

读宏章节时,不要纠结每个符号是不是特别玄,先问自己一个问题:这个宏接收哪些参数,返回什么代码结构?然后照着宏定义写出一个示例调用,模拟展开。

之后用macroexpand-1验证。如果展开结果和你预想的不一致,一定是某个反引号、逗号或&body位置理解错了。这时候翻开书对照,理解速度会快很多。

4.3 读取宏和代码遍历器可以晚点再看

读取宏(read macro)会改变读取器对源码文本的解析方式,代码遍历器会对展开后的代码做完整分析。这两部分是这个领域里最难的内容,也是这本书后段最硬核的部分。如果你的目标是日常工作里写一点宏、优化项目结构,前几章的宏技术已经够用;如果你对 DSL 编译前端感兴趣,再深入读取宏和代码遍历器。

不用因为这两章读不懂就怀疑自己。我个人建议先跳过,把前面能跑通的代码全部跑熟,回头再看会轻松很多。

4.4 每一章代码都自己敲一遍

看一遍永远不如敲一遍。尤其是宏章节,我建议把书里的每个宏定义都单独存一个.lisp文件,然后用几个不同的输入做展开测试。不是为了抄代码,是为了发现自己在逗号、反引号、嵌套层数上的理解漏洞。这类错误在展开结果里非常明显,看一次就记住了。

5. 读 PDF 时的实用姿势和笔记方法

5.1 浏览器内置阅读器最省事

这本书的 PDF 版本,用 Chrome 或 Edge 直接打开就行。内置阅读器支持关键词搜索、目录跳转、页面缩放,还能记住阅读位置。很多 PDF 工具的功能其实你用不到,浏览器自带的那套已经能覆盖 80% 的场景。

阅读时前 100 字内出现的代码示例,建议直接选中复制到本地文件,不要手打整段,减少无意义的输入错误。

5.2 打开慢或显示异常时的排查顺序

读 PDF 时如果遇到异常,按这个顺序排查:

现象优先检查
一直提示“正在准备以阅读”文件是否加载完整,网络是否正常,等几秒重试
打开后提示只读文件是否被设置成只读属性,保存一份副本再打开
文字能显示但搜不到可能是扫描版或字体编码问题,换正规阅读器再试
无法选中文字检查文件来源,不推荐用来路不明的提取工具
页面排版错乱确认是不是从官网下载的原始版本,而非第三方转换版

如果只是“文件无法保存”,最可能的原因是文件属性被标记为只读,复制一份重新命名即可。这和 PDF 本身无关,很多人在这一步浪费了不少时间。

5.3 想做摘录笔记,直接用 Markdown

我自己的做法是:PDF 负责阅读,Markdown 负责笔记。遇到关键代码,直接复制到 Markdown 代码块里,遇到概念解释,用自己的话写一句话总结。这样整本书读完后,你会得到一份精简过的个人笔记,后续复习就不用再翻整本 PDF。

不建议为了做笔记把整个 PDF 转成 Word。代码排版会乱,反而增加工作量。如果你确实需要把某个章节保留下来,用浏览器打印成新的 PDF,然后只保存需要的页码范围,是最干净的方式。

6. 真正读完这本书,最该避开的几个坑

6.1 别把宏当成普通函数到处用

这是最常见的问题。宏在编译期展开,不能像函数一样作为值传递,也不能在运行时动态创建。很多场景用普通函数就够了,强行上宏只会让代码更难阅读。

判断标准很简单:如果你能用一个函数实现,并且不需要控制求值顺序、不需要定义新语法、不需要捕获调用点上下文,就用函数。宏是最后的工具,不是第一选择。

6.2 调试宏先看 macroexpand

遇到宏运行结果不对,不要直接改调用方。先把这个宏单独拿出来,对它的输入做macroexpand-1,看展开后的代码是否符合预期。如果展开结果是对的,问题在运行时逻辑;如果展开结果就不对,问题一定在宏定义的反引号、逗号或参数结构上。

这个排查顺序能省掉大量时间。我一般会把展开结果的每一段都读一遍,确认progn是否插入到了正确位置,变量是否被意外捕获。

6.3 老书代码不一定原样跑在当前实现上

这本书写作时间比较早,代码示例基于当时的主流 Common Lisp 实现。大部分内容放到今天的 SBCL 上依然能跑,但个别库函数、包名或行为细节可能有差异。遇到编译报错,先看错误提示里提到的符号名,再查当前实现的文档,通常只是接口名调整,不是思路错了。

6.4 不要跳过闭包和求值顺序的章节

很多人读这种硬核书,喜欢直接跳到宏的高级章节,结果前面letlambda的细节没吃透,后面读一个例子卡半小时。这本书的章节是层层递进的,前几章的每个概念都在后面反复用。闭包里的变量生命周期、宏参数的求值时机、&environment的用途,这些如果只看一遍,后面很容易混。

如果时间有限,我会建议至少把前 3 章完整跑完,后面的内容按项目需求按需翻阅。整本读完固然好,但读完一遍不写代码,效果远不如把前面几章反复跑透。

这本书真正落地时,最该盯住的不是 PDF 能不能打开,而是你有没有一个能随时跑代码的环境。文件免费拿到很容易,环境搭好也不难,难的是把宏展开的每一步都亲手验证。建议先跑通单章代码,再考虑要不要把里面的 DSL 思路用到自己的项目里。踩过几次之后你会发现,很多问题不是书上的知识错了,而是闭包、宏展开和环境版本之间没有对齐。把这几层理顺,这本书就会从“看起来厉害”变成“确实能用来解决实际问题”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询