EVAL · READ → EVAL → PRINT · LISP 1958—2026

求值

E V A L

你写了很多年代码。你知道闭包会「捕获变量」,知道 async/await 能让异步代码看起来像同步的,知道 && 会短路,知道注解处理器能生成代码。这些你都会用。

但是:闭包捕获的到底是还是盒子?为什么 JS 里 for (var i…) 的三个闭包会一起看到 3?为什么你没法自己写一个 myIf(cond, a, b) 来代替 if?Kotlin 那个 Continuation<T> 接口,为什么叫这个名字?KSP 和 Babel plugin 为什么写起来那么痛苦,而它们要解决的问题,某门 1975 年的语言用四行就解决了?

这本书不劝你转去写 Lisp。它要做的是把你每天用的那些「特性」,还原成它们本来的样子 —— 在一门小到可以完全装进脑子里的语言里,你会亲眼看见环境链、看见续延、看见宏展开,最后亲手写出一个能跑的 eval。等你回到 Kotlin 和 JS,那些原本要靠背的规则,会变成你能推导出来的东西。

25
6
10个可视化
1个你亲手写的 eval

为什么是 Racket,而不是别的 Lisp

Lisp 有一大家子方言。Common Lisp 强大而庞杂,是工业级的老兵,但标准巨大、工具链要自己拼装,不适合当第一门。Clojure 好用、能挣钱、跑在 JVM 上,但它离经典 Lisp 挺远,而且它教不了你「语言是怎么造出来的」——那恰好是这本书的主题。Emacs Lisp 只有当你天天用 Emacs 时才划算。

Racket 是唯一一门把「教学」当成一等目标来设计的 Lisp。装完就能用(DrRacket 开箱即跑,不必先和编辑器搏斗),文档质量在整个函数式圈子里数一数二,而且它有一件别人没有的东西:#lang —— 「造一门语言」在 Racket 里不是口号,是当天就能做的事

更重要的是,它把 Lisp 的内核完整地留着:语法就是数据、求值规则小到能装进脑子、宏是语言的一部分而不是补丁。这三件事,正是你在 Kotlin 和 JS 里天天受其影响、却从来没机会看清楚的东西。

◆ 这本书的主线

你每天用的语言,是别人替你做完的一大堆设计决定。它们被打包成关键字发给你:if&&asyncyieldtailrec@Composable。你只能用,不能问「为什么是这样」,更不能改。

Racket 把这些决定拆开摆在桌上,然后把工具递给你。读完这本书,你不会成为 Lisp 程序员 —— 你会成为一个知道自己每天在用什么的 Kotlin / JS 程序员。

这本书假设你已经会什么

写给这样的人

  • 能熟练写 Kotlin / Java / JS / TS 中的至少一门
  • 用过 lambda、闭包、mapfilter,但说不太清它们的底层
  • 知道 async/await 怎么用,但没想过它凭什么能做到
  • 见过注解处理器、KSP、Babel plugin,觉得「这玩意儿怎么这么难写」
  • 想要的是看穿,不是多学一门语言的语法

不适合这样的人

  • 想学一门马上能在工作里用的新语言(那你该看 Clojure 或 TypeScript)
  • 完全没写过代码 —— 这本书假设你有编程基础
  • 只想拿 Racket 做算法题(可以,但这本书讲的不是这个)
  • 受不了「先绕远路,再回来」这种学法
▶ 关于「跑代码」这件事

这本书里的每一段 Racket 代码,都是给你粘进本机 DrRacket 里跑的,不是拿来看的。网页上不会有一个假 REPL —— 你本机那个是真的,而且比任何模拟器都准。

网页上只保留 REPL 给不了你的那类图:环境链长什么样、栈是怎么堆起来的、续延的那个「洞」在哪、宏展开时变量是怎么被捕获的。共 10 个,都是求值过程的内部状态 —— 那是 println 打不出来的东西。

读法

顺着读。这本书是有骨架的:卷 I 到卷 III 给你搭一台机器(读取器、求值器、环境),卷 IV 到卷 V 把这台机器的两个高级零件拆开(续延、宏),卷 VI 让你自己造一台。跳着读会散架。

每一章的最后都有一节 「回流」:这一课在 Kotlin 和 JS 里长什么样。那是你花时间读这本书要换回来的东西 —— 别跳过它。