探讨约翰·麦卡锡的符号化方法与 Lisp 的设计思想——列表、递归与垃圾回收——如何影响了人工智能与现代编程。

这不是一次“旧 AI”博物馆式参观。它更像一堂面向构建软件的实用历史课——对程序员、技术负责人和产品构建者都很重要,因为约翰·麦卡锡的思想塑造了我们对编程语言“用途”的理解。
Lisp 不只是新的语法。它是一个赌注:软件可以操纵思想(不仅仅是数字),语言设计的选择可以加速研究、产品迭代和整个工具生态。
理解麦卡锡遗产的一个有用方式是把它看成一个至今仍重要的问题:我们能多直接地把意图变成可执行系统——而不用淹没在样板代码、摩擦或意外复杂性中? 这个问题从 Lisp 的 REPL 回响到现代“聊天到应用”的工作流。
约翰·麦卡锡不仅因为推动人工智能研究而被记住,他坚持一种特定的 AI 观——系统应当能够操纵观念,而不只是计算答案。20 世纪 50 年代中期,他组织了达特茅斯夏季研究项目(当时提出了“人工智能”这一术语),并在 MIT 与后来的斯坦福推动 AI 工作。但他最持久的贡献或许是他不断提出的问题:如果把推理表达为程序,会怎样?
早期大多数计算成功是数值型的:弹道表、工程仿真、优化与统计学。这些问题天然适合算术。
麦卡锡瞄准的是不同的东西。人类推理经常处理像 “如果”、“因为”、“属于”、“是……的一种”、以及 “满足这些条件的所有事物” 这样的概念。这些并不自然地表示为浮点数。
麦卡锡的方法把知识视为符号(名字、关系、类别),把思考视为对这些符号的基于规则的变换。
一种高层次的区分是:数值方法回答“多少?”而符号方法试图回答“这是什么?”以及“基于已知信息能推出什么?”
一旦你相信推理可以被程序化,你就需要一种能舒适地表示规则、逻辑语句和嵌套关系并处理它们的语言。
Lisp 就是为此目的而建。它没有把思想强行塞入僵化的预定义数据结构,而是让代码与知识能够以相似的形状自然出现。这并非学术上的风格,而是把描述一个想法与执行一个过程之间搭起实用桥梁——这正是麦卡锡希望 AI 跨过的桥。
当麦卡锡和早期 AI 研究者说“符号化”时,并不是指神秘的数学。符号只是有意义的标签:像 customer 这样的名称、像 hungry 的词,或像 IF 与 THEN 的标记。符号之所以重要,是因为它们让程序处理观念(类别、关系、规则),而不仅仅是原始数字。
一个简单的比喻是:当你的世界是列与算术时,电子表格很棒;当你的世界是规则、类别、例外与结构时,符号系统更合适。
在许多程序中,42 与 "age" 的区别不在于数据类型,而在于该值代表的含义。符号给你可以比较、存储和组合且不丢失语义的东西。
这使得表示诸如“巴黎是一个城市”或“如果电池电量低,就去找充电器”之类的句子变得自然。
要对符号做有用的事情,你需要结构。Lisp 推广了一种非常朴素的结构:列表。列表只是有序的项的集合,而这些项本身也可以是列表。凭借这个想法,你可以表示句子、表单和树状知识。
下面是一个极小的概念示例(以 Lisp 风格呈现):
(sentence (subject robot) (verb needs) (object power))
它读起来几乎像英语:一个由主语、动词和宾语构成的句子。因为它是有结构的,程序可以挑出 (subject robot),或把 (object power) 替换为其他东西。
一旦信息以符号结构表示,经典 AI 任务就变得可处理:
关键转变在于:程序不只是计算;它在以可检查和可变换的形式操纵有意义的知识片段。
Lisp 的设计决策并没有只停留在学术界。它们影响了人们构建工具的方式以及探索想法的速度:
这些特性往往带来廉价的实验环境、原型更快变为产品、团队在需求变化时能更轻松适应的生态。
Lisp 起源于一个非常实用的设计问题:如何写出能够像处理数字那样自然地处理符号的程序?
麦卡锡并不是想造一个“更好的计算器”。他想要一种语言,使得 (is (parent Alice Bob)) 这样的表达可以像 (+ 2 3) 一样被存储、检查、变换和推理。
优先目标是让符号信息易于表示与操作。这带来了对列表与树状结构的关注,因为它们很好地映射人类用来表达含义的方式:句子、逻辑规则、嵌套类别与关系。
另一个目标是保持语言核心的小而一致。当语言的“特殊情况”更少时,你花在记忆规则上的时间更少,可以把精力放在组合思想上。Lisp 倾向于使用少量可以组合成更大抽象的基础构件。
关键见解是:程序和数据可以共享相同的结构。简单来说:如果你的数据是嵌套列表,你的程序也可以是嵌套列表。
这意味着你可以:
Lisp 也普及了一种心态:语言不必千篇一律。它们可以围绕问题领域设计——比如推理、搜索与知识表示——而且仍能在几十年里影响通用编程。
S-表达式(symbolic expressions)是 Lisp 的标志性思想:用单一一致的方式把代码与数据都表示为嵌套列表。
乍看之下,S-表达式只是把项目用括号包起来——有些项目是原子(名称和数字),有些是列表本身。那种“列表里再嵌列表”的规则就是重点。
因为结构是统一的,Lisp 程序从头到尾都由相同的构件构成。一个函数调用、一个像配置的片段、一个程序结构片段,都可以表示为列表。
这种一致性立即带来好处:
即便你从不写 Lisp,这个设计教训也很重要:当系统由一两个可预测的形式构建时,你花在对付边缘情况上的时间更少,能把精力放在构建上。
S-表达式鼓励组合性,因为小且可读的片段可以自然地组合成更大的。当你的程序“只是嵌套列表”时,组合思想往往意味着在另一个表达式中嵌套,或从可复用部分组装列表。
这会推动你走向模块化风格:写做一件事的小操作,然后把它们叠起来以表达更大的意图。
明显的缺点是不熟悉。对许多新手而言,括号密集的语法看起来很奇怪。
但优点是可预测性:一旦你理解嵌套规则,就能可靠地看到程序的结构——工具也能做到。这种清晰是 S-表达式 影响超出 Lisp 的重要原因之一。
用一个日常比喻理解递归最容易:把凌乱房间分成更小的“房间”清理。你不会一次解决所有问题。你拿起一件物品,把它放到该放的位置,然后对剩下的东西重复同样的动作。步骤简单,力量来自于重复,直到没有东西可做。
Lisp 支持这种思路,因为它的大量数据天然由列表构成:一个列表有“第一项”和“其余”。这种形态非常适合递归思维。
要处理一个列表,你处理第一项,然后把相同的逻辑应用到其余部分。当列表为空时,你停止——这是递归显得清晰而不是神秘的原因。
想象你要计算一个数字列表的总和。
就是这样。定义像普通英语那样,而程序结构与想法一一对应。
符号化 AI 常把表达式表示为树状结构(一个运算符和若干子表达式)。递归是“走”这棵树的自然方式:以相同方式求值左部和右部,持续下去直到遇到简单值。
这些模式影响了后来的函数式编程:小函数、清晰的基例和易于推理的数据变换。即便在 Lisp 之外,把工作分解为“做一步,然后在剩余上重复”的习惯也能带来更清晰的程序与更少的隐式副作用。
早期程序员常常要手动管理内存:分配空间、跟踪谁“拥有”它,并记得在合适时机释放。那类工作不仅拖慢开发速度,还造成一类难以重现且容易发布的 bug:内存泄漏会悄悄降低性能,悬挂指针会在很久之后崩溃程序。
约翰·麦卡锡为 Lisp 引入垃圾回收,目的是让程序员关注含义而不是账本工作。
高层来说,垃圾回收(GC)会自动找到运行程序无法再到达的内存片段——也就是说再也不会被使用的值——并回收这些空间。
与其问“我们是否恰好释放每个对象一次?”,GC 把问题变为“这个对象仍可达吗?”。如果程序无法到达它,它就被视为垃圾。
对于符号化 AI 的工作,Lisp 程序经常创建许多短期存在的列表、树和中间结果。手动内存管理会把实验变成不断清理资源的战斗。
GC 改变了日常体验:
关键思想是:语言特性可以是团队乘数——减少调试神秘内存损坏的时间,就能把更多时间投入到改进实际逻辑上。
麦卡锡的选择没有只停留在 Lisp 内。许多后来的系统采用了 GC(及其变体),因为权衡常常是值得的:Java、C#、Python、JavaScript 运行时和 Go 都依赖垃圾回收来让大规模开发更安全、更快速——即便在关注性能时也是如此。
在 Lisp 中,一个表达式是以一致形态写出的代码片段(通常是列表)。求值就是决定该表达式意味着什么以及它产生什么的过程。
例如,当你写“把这些数相加”或“用这些输入调用这个函数”时,求值器按一小组规则把表达式变成结果。把它想象成语言的裁判:决定下一步做什么、以何种顺序以及何时停止。
麦卡锡的关键动作不仅仅是发明新的语法——而是保持“意义引擎”紧凑且规整。当求值器由几条清晰规则构成时,会发生两件好事:
这种一致性是 Lisp 成为符号 AI 试验场的原因之一:研究者能快速尝试新表示与控制结构,而不必等编译器团队重新设计语言。
宏是 Lisp 让你自动化重复代码形态的方法,而不仅仅是重复的值。函数帮助你避免重复计算;宏帮助你避免重复结构——像“做 X,但同时记录日志”或“定义一个用于规则的小型语言”这样的常见模式。
实际效果是 Lisp 能从内部演化出新的便利。许多现代工具反映了这一思想——模板系统、代码生成器与元编程特性——因为它们支持同一个目标:更快的试验与更清晰的意图。
如果你想知道这种心态如何影响日常开发工作流,请参见 /blog/the-repl-and-fast-feedback-loops。
Lisp 的吸引力很大一部分来自于你与它的工作方式。Lisp 推广了 REPL:Read–Eval–Print Loop。通俗来说,这像是在与计算机对话。你输入一个表达式,系统立即运行它,打印结果,并等待下一次输入。
与其写完整个程序、编译、运行然后追查错误,你可以一步步尝试想法。你可以定义一个函数,用几个输入测试它、微调它并再次测试——几秒钟之内完成。
这种节奏鼓励实验,这对早期 AI 工作尤为重要,因为那时你常常不知道正确的方法是什么。
快速反馈把“大赌注”变成“小检查”。对研究来说,它让探索假设与检查中间结果变得更容易。
对产品原型来说,它降低了迭代成本:你可以用真实数据验证行为,更早发现边界情况,并在无需漫长构建周期的情况下改进功能。
这也是为什么现代的即刻编码工具吸引人:它们本质上是对反馈回路的激进压缩。例如,Koder.ai 使用基于代理的聊天界面,将产品意图快速转为可运行的 web、后端或移动代码——常常让“尝试 → 调整 → 再尝试”的循环更像 REPL 而非传统流水线。
REPL 的思想今天以各种形式出现:
不同工具,共同原则:缩短思考到可见结果的距离。
当团队在探索不确定需求、构建数据密集型功能、设计 API 或调试复杂逻辑时,REPL 式工作流价值最大。如果工作涉及快速学习——关于用户、数据或边界情况——交互式反馈并非奢侈,而是倍增器。
Lisp 并没有靠成为人人的日常语法而“获胜”。它靠播下的思想悄然成为许多生态的常态。
Lisp 把默认视作的概念——函数作为值、大量使用高阶操作、通过组合小部件构建程序——如今广泛出现。即便是与 Lisp 外观差别很大的语言,也鼓励 map/filter 风格的变换、不变数据习惯和类似递归的思维(常通过迭代器或折叠实现)。
关键心态是:把数据变换当做管道,把行为当做你可以传递的东西。
Lisp 让程序易于作为数据表示。这种心态今天体现在我们如何构建和操作 AST(抽象语法树)以支持编译器、格式化器、linter 与代码生成器。当你处理 AST 时,你在做“代码即数据”的近亲工作,即便这些结构是 JSON 对象、类型化节点或字节码图。
相同的符号化方法驱动实用自动化:配置格式、模板系统与构建流水线都依赖可被工具检查、变换与验证的结构化表示。
现代的 Lisp 家族语言(广义上:当代 Lisp 与受 Lisp 启发的工具)持续影响团队如何设计内部 DSL——用于测试、部署、数据处理或 UI 的小型迷你语言。
在 Lisp 之外,宏系统、元编程库与代码生成框架也追求同样结果:在不重写全部的情况下扩展语言以适配问题。
一个务实结论是:语法喜好会变,耐久的思想——符号化结构、可组合函数与可扩展性——会持续为代码库带来价值。
Lisp 的声誉在“聪明”与“难读”之间摇摆,常基于道听途说而非日常经验。真实情况更平凡:Lisp 在适合的场景很强大,在不合适的场景则显得不便。
对新手来说,Lisp 的统一语法可能让人感觉像是在看程序的“内部”而不是抛光过的表面。这种不适是真实存在的,尤其是当你习惯了语法在视觉上区分不同构造的语言时。
但从历史上看,Lisp 的结构也是重点:代码与数据共享相同形状,便于变换、生成与分析。在良好编辑器支持(缩进、结构化导航)下,Lisp 代码常按形状而非通过数括号来阅读。
一个常见的刻板印象是 Lisp 天生很慢。历史上一些实现确实在与低级语言比较时吃亏,动态特性可能带来开销。
但把“Lisp”当成单一性能档位并不准确。许多 Lisp 系统长期支持编译、类型声明与严肃优化。更有用的表述是:你需要多少对内存布局、可预测延迟或原始吞吐量的控制——以及你的 Lisp 实现是否面向这些需求?
另一个公平的批评是生态适配问题。根据 Lisp 方言与领域的不同,库、工具与人才可能比主流栈更少。如果你需要在广泛团队中快速交付,这一点可能比语言优雅更重要。
不要用刻板印象评判 Lisp,而应独立评估它的基本思想:统一结构、交互式开发、把宏作为构建领域特定抽象的工具。即使你从不交付 Lisp 系统,这些概念也能磨炼你对语言设计的思考以及在任意语言中写代码的方式。
麦卡锡不仅给我们留下了一门历史语言——他留下了一套习惯,这些习惯仍让软件更易于改变、解释与扩展。
偏好简单的核心而非巧妙的表面。 一小组互不干扰的构件更易学、也更难被搞坏。
保持数据形态一致。 当许多东西共享表示(如列表/树)时,工具更简单:打印器、调试器、序列化器与变换器都能复用。
在有帮助时把程序当作数据(反之亦然)。 如果你可以检查与变换结构,就能构建更安全的重构、迁移与代码生成器。
自动化枯燥工作。 垃圾回收是经典案例,但更广泛的观点是:投资于能防止整类错误的自动化。
优化反馈回路。 交互式求值(REPL 风格)鼓励小实验、快速验证与对行为的更好直觉。
把可扩展性作为一级设计目标。 Lisp 宏是一种答案;在其他生态中,这可能是插件、模板、DSL 或编译时变换。
在选择某个库或架构之前,拿一个真实功能(例如“折扣规则”或“工单路由”),把它画成树。然后把那棵树重写为嵌套列表(或 JSON)。问自己:节点是什么、叶子是什么,以及你需要哪些变换?
即便不使用 Lisp,你也可以采纳这种思维:构建 类 AST 的表示、使用 代码生成 处理重复的胶水代码、并标准化 数据优先的流水线(解析 → 变换 → 执行)。许多团队通过把中间表示显式化就能获得好处。
如果你喜欢 REPL 原则但在主流栈中交付产品功能,也可以把精神引入工具链:紧密迭代循环、快照/回滚与在快速变更中保持控制。Koder.ai 例如包含规划模式、快照与回滚,使快速迭代更安全——这是 Lisp“快速变更但保持可控”理念的一个运作回声。
麦卡锡留下的持久影响是:当我们把推理本身变得可编程,并把从想法到可执行系统的路径尽可能直接化时,编程变得更强大。
符号化思维是直接表示概念和关系(例如 “customer”、“is-a”、“depends-on”、“if…then…”),然后对这些表示应用规则和变换。
当你的问题充满结构、例外与含义(如规则引擎、规划、编译器、配置、工作流逻辑)时,符号化思维最为有用,而不是仅仅处理算术问题。
麦卡锡推动的是把推理用程序表达的想法——不只是计算。
这种视角影响深远:
列表是一种极简且灵活的“由部分组成的事物”的表示方式。因为列表元素本身可以是列表,你自然得到树形结构。
这使得你可以:
S-表达式为代码和数据提供了统一的形态:嵌套列表。
这种统一性让系统更简单,因为:
宏自动化的是重复的代码结构,而不仅仅是重复的计算。
当你要:
时,使用宏很合适。
如果仅仅需要复用逻辑,函数通常是更好的选择。
垃圾回收(GC)会自动回收程序无法再访问的内存,从而减少一类常见错误(悬挂指针、重复释放等)。
当程序频繁创建短命结构(列表/树/AST)时,GC 尤其有用,因为你可以在不先设计内存所有权策略的情况下快速试验与重构。
REPL 缩短了“思考 → 尝试 → 观察”的循环。你可以定义一个函数,运行它,调整它,然后立即再次运行。
要在非 Lisp 技术栈中获得类似好处:
相关阅读:/blog/the-repl-and-fast-feedback-loops
许多现代工作流复用了相同的基本思想:
map/filter、组合)即便你从不交付 Lisp,日常工作中很可能已在使用由 Lisp 种下的习惯。
常见的权衡包括:
实用的做法是依据领域与约束评估适配性,而不是听信声誉。
做这个 10 分钟练习:
这通常能揭示何处适合“代码即数据”模式、规则引擎或类 DSL 配置,从而简化系统设计。