1.1 诞生背景:符号处理与人工智能的萌芽
1958年,约翰·麦卡锡在麻省理工学院设计LISP时,其核心目标并非计算数值,而是处理符号——即逻辑、代数表达式和自然语言中的抽象符号。当时,人工智能研究刚刚兴起,麦卡锡希望创建一种语言,能像人类一样进行符号推理、模式匹配和问题求解。FORTRAN虽已存在,但专注于数值计算,不适合符号操作。LISP的设计灵感来自阿隆佐·丘奇的lambda演算,将“列表”作为基本数据结构,并引入递归作为主要控制机制,这使它能更自然地表达AI算法。
1.2 早期版本:LISP 1、LISP 1.5与Lisp 2
LISP的第一个实现是LISP 1(1960年),奠定了S表达式和基本函数(CAR、CDR、CONS)的基础。随后LISP 1.5(1962年)增加了垃圾回收、数值函数和格式化输出,成为首个广泛使用的版本。麦卡锡曾规划过Lisp 2——一种包含类型系统、数组和编译器的更复杂语言,但由于缺乏资金和实现难度,最终未能完成。这些早期版本在IBM 704、PDP-6等机器上运行,受限于内存和性能,却展示了符号计算的强大潜力。
1.3 黄金时代:LISP机器与MIT AI实验室
1970年代至1980年代,LISP成为MIT AI实验室的核心语言。研究人员开发了专门的LISP机器——例如Symbolics 3600和Xerox Lisp Machine——这些硬件专门运行LISP,集成了编译器、图形界面和高效的内存管理。LISP机器社区形成了封闭但充满活力的生态:程序员在“Lisp大教堂”中共享代码、理念,并诞生了首个商用面向对象系统Flavors和Common Lisp Object System的前身。这一时期,LISP被用于专家系统、自然语言理解和数学证明(如Boyer-Moore定理证明器)。
1.4 “AI寒冬”与商业化起伏
1980年代末,随着人工智能投资泡沫破裂(AI寒冬),LISP机器的市场份额骤降。通用硬件(如基于Unix的工作站和PC)性能提升,使专用LISP硬件失去成本优势。Symbolics等公司倒闭或转型,LISP社群萎缩。但LISP并未死亡——它转入学术与嵌入式领域。Common Lisp标准(ANSI X3J13)于1994年成型,试图统合方言,但商业化应用停滞。直到21世纪初,Clojure和Racket等新方言引爆了现代复兴。
1.5 现代复兴:Clojure、Racket等新方言
2007年,Rich Hickey发布Clojure,它将LISP哲学与现代Java虚拟机(JVM)结合,提供不可变数据结构和并发支持,吸引了一批Web和云计算开发者。与此同时,Racket(原名PLT Scheme)强调语言构建工具,成为编程教育领域的明星。Common Lisp也通过SBCL、CCL等高效实现和Quicklisp包管理器,在游戏、生物信息学和Web后端中重获生存空间。LISP的“第二次青春”证明,其核心思想——如宏、符号计算和交互式开发——在云原生和函数式编程浪潮中依然鲜活。
2.1 S表达式(Symbolic Expression)
2.1.1 原子(Atom)与列表(List)
S表达式是LISP代码和数据的统一表示形式。它包含两类元素:原子和列表。原子是不可分割的最小单元,包括数值(如42)、符号(如x、+)和字符串(如"hello")。列表是由零个或多个S表达式组成的序列,用括号包裹并用空格分隔,例如(+ 1 2)是一个列表,其中+是原子符号,1和2是数值原子。空列表写作()或NIL。
2.1.2 括号的嵌套规则与求值顺序
列表可以任意嵌套,例如(* (+ 2 3) (- 5 1))。LISP的求值规则:列表的第一个元素被视为操作符(函数或宏),剩余元素为参数;原子中的符号作为变量名或常量。计算时先对参数求值(除非特殊规则),再将结果传入函数。例如,(+ 1 (* 2 3))会先计算(* 2 3)得到6,再计算(+ 1 6)得到7。这种前缀表示法(波兰表示法)避免了运算符优先级歧义,但也导致了标志性的括号堆积。
2.2 基本函数与操作符
2.2.1 CAR、CDR与CONS:列表的原子操作
LISP的元组合来自3个函数:
- CAR:返回列表的第一个元素。例如(car '(1 2 3)) → 1。
- CDR:返回列表的剩余部分(去掉第一个元素)。例如(cdr '(1 2 3)) → (2 3)。
- CONS:将元素与列表组合成新列表。例如(cons 1 '(2 3)) → (1 2 3)。
这些函数在历史上有趣的发音(“car”读作“卡”,“cdr”读作“库德-厄”),且衍生出组合如CADR(取第二个元素)、CADDR(取第三个元素)等,形成了一套精致的列表处理语言。
2.2.2 算术、比较与逻辑函数
算术函数包括+、-、*、/,以及数学库函数如EXPT(指数)、SQRT(开方)等。比较函数包括=、=、<、>、CHAR=等,返回布尔值(Common Lisp中用T和NIL表示真、假)。逻辑函数有AND、OR、NOT,它们遵循短路求值规则。例如(and (> 3 2) (< 1 2)) → T。
2.2.3 定义函数:DEFUN与LAMBDA
DEFUN用于定义命名函数,格式为(defun 函数名 (参数列表) 函数体...)。例如(defun square (x) (* x x))。LAMBDA用于创建匿名函数,可作为参数传递,格式为(lambda (参数) 函数体)。例如(mapcar (lambda (x) (* x x)) '(1 2 3)) → (1 4 9)。
2.3 变量与作用域
2.3.1 动态作用域(Dynamic Scope)与词法作用域(Lexical Scope)
早期LISP(如MacLisp)采用动态作用域:变量的值由调用时的上下文决定,而非定义时的位置。这虽灵活,但易导致难以追踪的副作用。Common Lisp默认使用词法作用域(闭包),变量在定义时确定绑定,仅对所在区域可见。Scheme和Clojure严格使用词法作用域。动态作用域通过特殊变量(如DEFVAR声明的全局变量)或PROGV机制保留。
2.3.2 全局变量与局部变量(LET、LET*)
全局变量用DEFVAR或DEFPARAMETER声明,用户可随时修改(但前者会因重载产生风格警告)。局部变量通过LET绑定:(let ((x 1) (y 2)) (+ x y))。LET*允许按序绑定,后一变量可引用前一个值:(let* ((x 1) (y (+ x 1))) y) → 2。LET创建的变量在退出块后失效。
2.4 控制流
2.4.1 条件分支(IF、COND、WHEN、UNLESS)
- IF:三参数形式
(if 条件 真分支 假分支)。 - COND:更优雅的多分支,
(cond (条件1 动作1) (条件2 动作2) (t 默认动作)),T用作始终真。 - WHEN和UNLESS:分别仅在条件为真或假时执行单个动作(没有else分支)。
例如:(if (> x 0) "positive" "non-positive")。
2.4.2 递归与迭代(DO、LOOP、MAPCAR)
LISP崇尚递归,但提供多种迭代机制:
- DO:通用迭代宏,格式繁琐但灵活。
- LOOP:英文式宏,可混合计数、条件、收集等,例如
(loop for i from 1 to 10 collect i)。 - MAPCAR:函数式遍历,对列表每个元素应用函数并返回结果列表。
Common Lisp的LOOP因可读性强而广受喜爱,但也因可包含副作用而被批评为“反功能式”。
2.5 代码即数据:QUOTE与反引用
LISP的“代码即数据”范式源于QUOTE,它将表达式视为数据而非代码。(quote (a b c))等价于'(a b c),返回未经求值的列表。反引用( quasiquote)用反引号()与逗号(,)结合,允许在模板中插入可求值部分。例如:(let ((x 2)) (a ,x c)) → (a 2 c)。这一机制对宏系统至关重要,使程序能在编译期动态生成代码。
3.1 函数式编程范式
3.1.1 一级函数与高阶函数
LISP中函数是一级公民:可像数据一样赋值给变量、作为参数传递、作为返回值。高阶函数如MAPCAR、FILTER、REDUCE接受函数参数,使组合逻辑成为可能。例如(mapcar #'+ '(1 2) '(3 4))使用内置加函数将两个列表逐元素相加。
3.1.2 纯函数与副作用
纯函数式风格在Clojure和Scheme中强调,但Common LISP并未强制禁止副作用。函数可修改全局变量、执行I/O或改变数据结构。标志性的破坏性操作如RPLACA、RPLACD(修改cons cell)被视为非纯函数,但在某些场景下可提升性能。提倡函数式编程的方言(如Racket)通过no-side-effects约束限制。
3.1.3 惰性求值(部分方言支持)
标准Common LISP和Scheme采用急切求值。但Scheme的R7RS库支持delay和force实现惰性。Clojure原生支持惰性序列,如(take 10 (range))创建无限序列。惰性求值可延迟计算直至需要,优化内存占用并支持无限数据结构。
3.2 宏系统
3.2.1 宏定义与编译期代码生成
宏是LISP最具特色的功能之一。宏定义(DEFMACRO)接收未求值的代码作为参数,返回新的S表达式,在编译前展开。这使得开发者能扩展语言语法,实现领域特定语言。宏展开发生在读取期之后、编译期之前,因此宏函数可以调用所有LISP函数。
例如,(defmacro while (test &body body) (loop while ,test do ,@body))`定义了新的循环结构。
3.2.2 经典的宏案例:WITH-OPEN-FILE、循环宏
Common LISP的WITH-OPEN-FILE宏简化文件操作:(with-open-file (stream "file.txt" :direction :output) (format stream "hello"))会自动管理文件关闭(类似Python的with语句)。循环宏如LOOP本身就是用宏实现的复杂DSL。简化版DO遍历宏:(defmacro for ((var start end) &body body) ...)。
3.2.3 卫生宏(Hygienic Macros)与反卫生宏
卫生宏在展开时避免变量名冲突,由Scheme的define-syntax实现(如syntax-rules)。Common LISP的DEFMACRO默认不卫生,开发者需手动使用GENSYM生成唯一符号以避免捕获。反卫生宏(又称“怪诞宏”)有意识地破坏封装,实现巧妙但危险的技巧,是LISP社群中“炫技”文化的来源。
3.3 交互式开发环境
3.3.1 REPL(读取-求值-打印-循环)
LISP的REPL(Read-Eval-Print Loop)允许开发者输入代码立即执行,查看结果。这是交互式开发的基石。在Common LISP实现(如SBCL)中,REPL支持历史记录、补全和多线程。Clojure的REPL集成在Clojure REPL和Leiningen项目中,支持热重载。
3.3.2 增量编译与热加载
LISP环境支持增量编译:修改函数或宏后立刻重新编译并生效,无需重启整个程序。这在游戏开发和服务器端是巨大优势。例如,在Clojure中,通过开发工具Cider,开发者可修改函数并实时观察输出。
3.3.3 调试与跟踪(TRACE、STEP)
LISP提供强大的调试工具。TRACE可打印函数调用及返回值:(trace my-function)。STEP逐条执行代码并允许干预。Common LISP的条件系统(Condition System)更进一步,允许在运行时捕获错误并选择多种恢复策略,而非简单的崩溃。
3.4 内存管理
3.4.1 符号表与原子存储
LISP运行时维护一张符号表,将字符串符号(如’x)映射到其属性列表(property list)。符号可存储值、函数定义和任意属性,如(get 'x 'color)。原子存储(oblist)本质上是一个哈希表,使符号查找高效,但也占用了内存。
3.4.2 垃圾回收(Stop-the-world、增量GC)
LISP的早期版本已实现自动垃圾回收(比Java早30年)。Simple GC是停止-世界模式,当cons cell耗尽时暂停所有程序执行。现代实现(如SBCL的Generational GC)采用分代收集和增量标记,减少停顿时间。Clojure依赖JVM的GC。不同方言回收算法各异,但都追求降低内存碎片和暂停影响。
3.4.3 缺点单元(Cons Cell)的结构
| Cons cell是最基本的数据存储单元:每个cell由两个指针(car和cdr)组成。例如,列表(1 2 3)在内存中表示为:`[1 | →] [2 | →] [3 | NIL]`。每个cons cell占用固定大小(通常两个机器字),无类型标签,是LISP灵活但内存不友好的根源。共享子列表(如通过RPLACD修改)可能带来数据不安全,也为高级构造提供了可能。 |
|---|
4.1 人工智能与符号推理
4.1.1 专家系统与规划器
1960-80年代,LISP是专家系统的首选语言。例如Mycin(医疗诊断)、XCON(VAX计算机配置)和STRIPS(自动规划器)都用LISP实现。这些系统利用符号存储规则和事实,通过模式匹配和回溯推理。
4.1.2 自然语言处理(早期尝试)
LISP被用于早期自然语言处理:如SHRDLU(块世界语言理解)、ELIZA(心理治疗对话系统)。这些程序通过规则匹配和简单语法分析演示了符号系统的潜力,但其局限性也在后来被统计方法所取代。
4.2 Emacs扩展:Emacs Lisp(Elisp)
4.2.1 编辑器内嵌脚本环境
Emacs以嵌入Elisp解释器闻名,使编辑器高度可定制。用户可通过修改.el文件实现新功能(如文件管理、版本控制集成)。Elisp是一个动态作用域的LISP方言(但部分版本支持词法作用域),与GNU Emacs紧密结合。
4.2.2 典型插件开发(Org-mode、Magit)
Org-mode(笔记、项目管理、日程)、Magit(Git前端)和Evil(Vim模拟)都是用Elisp编写的复杂插件。这些插件充分利用了Emacs的钩子系统、缓冲本地变量和按键绑定,形成了庞大的Emacs社区。
4.3 教育科研
4.3.1 SICP(计算机程序的构造与解释)教材
麻省理工学院经典教材《Structure and Interpretation of Computer Programs》(SICP)使用Scheme作为教学语言,系统讲解计算机科学的核心概念(抽象、解释器、元循环求值、流、惰性求值等)。许多计算机科学家视之为编程启蒙圣经。
4.3.2 编程语言理论研究
LISP的简单语法和宏系统使其成为研究编程语言理论(类型系统、模块化、形式化语义)的方便实验平台。例如,Tiny Scheme、Racket的#lang机制允许人们快速搭建语言原型。
4.4 商业与工业零星应用
4.4.1 自动排版与CAD(AutoLisp)
AutoCAD内嵌AutoLisp(后进化为Visual LISP),使设计师能编写自动化脚本生成图形。在建筑、机械领域,AutoLisp被用于批量修改、参数化设计,成为LISP在非程序员中留存最久的应用领域。
4.4.2 金融领域(风险分析、算法交易)
少数金融机构(如对冲基金Renaissance Technologies)使用Common Lisp构建交易系统和风险模型。其优势在于快速原型、交互式开发和宏抽象的易维护性。但其招聘困难和安全问题限制了更广泛采用。
5.1 Common Lisp
5.1.1 ANSI标准化与CLOS(Common Lisp Object System)
Common Lisp于1994年成为ANSI标准,统一了多种方言。其核心创新之一是CLOS(Common Lisp Object System),包含多方法(defmethod)、类(defclass)和通用的组合机制。CLOS是第一个将面向对象与函数式融合的语言系统,影响了后来的Python等。
5.1.2 实现:SBCL、CCL、LispWorks
- SBCL(Steel Bank Common Lisp):开源、高性能,使用原生编译器,适用于生产环境。
- CCL(Clozure Common Lisp):支持多平台,与Mac OS X深度整合。
- LispWorks:商业实现,配备GUI构建器和数据库连接,面向企业开发者。
5.2 Scheme
5.2.1 极简主义与词法作用域
Scheme由Guy Steele和Gerald Jay Sussman于1975年设计,以极简著称。它严格使用词法作用域,只提供少量内置类型(如过程、列表、向量),却支持强大的宏系统(syntax-rules)和连续传递风格(call/cc)。
5.2.2 标准:R5RS、R6RS、R7RS
- R5RS(1998):最普及的标准,小巧清晰。
- R6RS(2007):新增字节向量、异常处理,但增加了复杂性。
- R7RS(2013):分大、小两套,小版回归R5RS的精简风格。主流实现包括chez scheme、guile和chicken。
5.3 Clojure
5.3.1 JVM平台与不可变数据结构
Clojure运行于JVM和CLR,底层可调用Java库。其标志性特性是持久化不可变数据结构(如vector、map、set),通过共享内存实现高效更新。语法类似Common Lisp,但去除了许多复杂特性(如CLOS、GO子句)。
5.3.2 并发原语(Atom、Ref、Agent)
Clojure提供三种并发机制:Atom(独立值)、Ref(多变量协同,支持事务内存STM)和Agent(异步动作)。这些原语结合不可变数据,避免了共享锁带来的问题,使Clojure在高并发应用中表现突出。
5.4 其他方言
5.4.1 Racket(教学与语言构建工具)
Racket(前身PLT Scheme)专注于快速建立领域特定语言(DSL)。它提供语言构造工具#lang、丰富的库和DrRacket IDE。Racket被用于在计算机教育中教授语言设计(如Beautiful Racket)。
5.4.2 AutoLisp/Visual Lisp(AutoCAD内嵌)
AutoLisp(1986年)是AutoCAD的脚本语言,语法简化为子集(只有C、MF等基本函数)。Visual LISP(2000年)增加了IDE、面向对象扩展。至今许多设计公司仍在使用AutoLisp绘制建筑和工程图纸。
5.4.3 Emacs Lisp(生态特殊)
Emacs Lisp(Elisp)独立于主流LISP标准,使用动态作用域(新版本可选词法作用域)、无类型系统、只有少量数据结构。由于其与Emacs的绑定,Elisp拥有大量插件,但代码质量参差不齐。
6.1 括号地狱(Empty parentheses syndrome)
6.1.1 视觉冲击与编辑器大战
LISP代码因层层嵌套的括号而被称为“括号地狱”。新手常被数十个)结尾的语句吓倒。这引发了编辑器的军备竞赛:Emacs、Vim、Sublime等都提供高亮和自动配对功能。LISP程序员发明了“拼写括号”的口诀(如“先写括号再填充内容”),以及“括号计数”这一初级竞赛。
6.1.2 颜色配对插件与Rainbow Parentheses
Rainbow Parentheses插件将不同层级的括号染上彩虹色,使嵌套结构一目了然。这一模式被推广到PyCharm、VS Code等现代编辑器。LISP程序员自称“彩虹驯兽师”,调侃只有学会了“看颜色”才真正掌握LISP。
6.2 LISP机器与逝去的硬件时代
6.2.1 Symbolics与Xerox的角逐
1970-80年代,LISP机器市场由Symbolics和Xerox主导。Symbolics Generation(如3645型)配有专用Lisp芯片,性能远超前通用硬件。Xerox的LISP机器(Interlisp-D)则强调图形用户界面(如Smalltalk风格的窗口)。这场角逐最终因通用硬件的摩尔定律而终止,但留下了“若我们用LISP机器,世界会更好”的社团观点。
6.2.2 今日的LISP硬件怀旧
如今,LISP机器成为收藏品和模拟器项目(如lisp-machine.info)。爱好者通过仿真模拟运行Genera操作系统。LISP机器论坛上月活跃用户上千,他们讨论“上古时代的IDE”、“旧式光纤鼠标”,形成了一种赛博怀旧文化。
6.3 经典语录与社群文化
6.3.1 “Lisp是可编程的编程语言”
这一名言强调宏系统的威力:程序员不仅写程序,还能自定义语言语法。在LISP社区,讨论宏的优雅和危险是永恒话题。
6.3.2 “任何足够复杂的C程序都包含一个临时的、非正式的、充满Bug的Lisp实现”
出自格林斯潘(Greenspun)的调侃:当C程序试图用结构体、函数指针和递归实现元编程时,实际上在“低劣地”模仿LISP。这道出了LISP对思想的影响:许多特性(垃圾回收、泛型、闭包)后来被主流语言借鉴。
6.3.3 围绕宏的炫技与安全警告
LISP程序员常以编写令人眼花缭乱的宏为荣,如“单行编译器”或“内嵌XML解析器”。同时,宏也带来安全风险:不卫生宏易导致代码注入,在Web应用中可能成为漏洞。因此社区有口头禅:“宏像核武器——强大但别炸到自己。”
7.1 当代实现与生态系统
7.1.1 Quicklisp包管理器
Quicklisp(2011年)是Common Lisp的事实标准包管理器,提供数千个库,支持自动依赖解析。它使得LISP开发从“手工翻找源代码”变为快速安装。类似Clojure的Leiningen和Clojars,提供了稳定的构建链。
7.1.2 现代IDE支持(Slime、Sly、LSP)
SLIME(Superior Lisp Interaction Mode for Emacs)是最流行的Common Lisp IDE,支持REPL、调试器、补全。Sly是其分叉,提供更现代的界面。LSP(Language Server Protocol)的LSP支持已通过cl-lsp实现,使VS Code、IntelliJ也能编写LISP。
7.2 新兴趋势
7.2.2 与WebAssembly的整合
LISP的编译到WebAssembly(WASM)尚在早期,但已有方案(如Racket的WASM后端、ClojureScript的WASM编译)。这能让LISP代码在浏览器中接近原生速度运行,可能开启老年语言的新战场。
7.2.3 在云原生与微服务中的尝试
Clojure在云原生栈中表现活跃,例如与Kubernetes、Docker配合,用Ring(Web框架)和Compojure开发微服务。Common Lisp的Hunchentoot、Wookie作Web服务器,但部署工具链较成熟社区薄弱。未来对Serverless和边缘计算的支持可能吸引新用户。
7.3 争议与局限
7.3.1 性能瓶颈与JIT编译
LISP的动态特性和运行时开销导致其在数值计算密集型任务上不如C/Rust。JIT编译(如ROME-LLVM、SBCL的本地代码生成)部分缓解了问题,但在实时渲染或科学计算中仍显不足。
7.3.2 开发者招聘与学习曲线
学习LISP需要理解递归、宏和函数式思维,初始门槛高于主流语言。招聘市场上,Common Lisp和Clojure开发者稀缺,企业面临培训成本。这反过来限制了项目规模扩张。
8.1 经典教材
8.1.1 《Structure and Interpretation of Computer Programs》(SICP)
Harold Abelson、Gerald Jay Sussman和Julie Sussman合著。使用Scheme深入讲解计算抽象、解释器构建、元循环和流处理。
8.1.2 《ANSI Common LISP》
Paul Graham著。系统介绍Common Lisp语言,强调实用编程技巧(如宏、高阶函数和迭代),适合作为LISP入门的进阶读物。
8.2 在线资源
8.2.1 Common Lisp Hyperspec
Common Lisp的官方在线标准参考,以HATEOAS风格超链接组织函数、宏和变量,是日常开发必备手册。
8.2.2 Clojure官网与社区指南
Clojure官网(clojure.org)提供官方文档、API和教程。社区主导的Clojure.org设计指南和Clojure Style Guide是作业与项目参考。
8.3 相关标准与论文
- Lambda论文:John McCarthy的"Recursive Functions of Symbolic Expressions and Their Computation by Machine"(1960),阐述了Lisp的数学基础。
- ANSI X3J13标准(1994):Common Lisp的官方标准文档。
- R7RS大型标准:Scheme最新标准,提供了模块、异常和I/O规范。
- Clojure白皮书:Rich Hickey的“Clojure in Practice”系列文章,解释语言设计动机。