:从语法基础到 CLOS 与宏的完整实战指南)
文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载导读本文基于 learnxinyminutes-docs 仓库中的西班牙语教程 es/common-lisp.md与英文原版 common-lisp.md 同源编写面向想要快速掌握 Common LispCL的开发者。Common Lisp 是一门通用、多范式multi-paradigm的工业级编程语言常被称为可编程的编程语言programmable programming language——因为它的宏系统允许你直接扩展语言本身的语法。读完本文你将掌握 CL 的 S-表达式语法、核心数据类型与操作符、变量作用域、列表与哈希表、函数定义、相等性语义、控制流、CLOS 面向对象编程以及宏编写的完整实战技能。仓库采用以带注释的合法代码为教程的独特形式参见 README.md每篇文档都是可运行、可交互的代码片段。下面我们按原文的章节脉络逐层深入并在关键位置结合仓库源码给出更细致的解释。说明本文所有代码示例均可在 SBCL 等标准实现的 REPL 中直接运行验证文档的 YAML 头部contributors / translators 字段由仓库 lint/frontmatter.py 统一校验保证元数据格式规范。开篇Common Lisp 是什么以及从哪里开始Common Lisp 是一门适用于广泛工业应用场景的通用目的、多范式语言。它拥有统一的数据与代码表示S-表达式、交互式开发环境REPL、强大的宏系统与元对象协议这些特性使它既适合快速原型也适合构建长期演进的大型系统。原文推荐的学习路径与经典读物包括Practical Common Lisp——经典的入门起点Land of Lisp——通过游戏开发学习 LispCommon Lisp Recipes——面向最佳实践的较新参考书。以上为原文列出的外部读物信息仅作学习方向参考本文的示例与验证均以当前仓库文档为准。0. 语法基础ATOM、S-表达式与注释一般形式CL 的语法只有两种基本构件**ATOM原子**与S-EXPRESSIONS-表达式。通常成组的 S-表达式被称为form。10 ; 一个 atom求值得到它自身 :thing ; 另一个 atom求值得到符号 :thing t ; 另一个 atom表示 true ( 1 2 3 4) ; 一个 s-expression (4 :foo t) ; 另一个 s-expression可以看到数字、关键字符号、布尔值都是原子而(f x y ...)形式的列表则是函数调用式的 S-表达式。注释规范单行注释以分号;开头。原文给出了一套约定俗成的分级用法四个分号;;;;文件级注释如;;;; life.lisp三个分号;;;段落/小节描述两个分号 ;;定义内部的说明一个分号;单行行尾注释。;;;; life.lisp ;;; Foo bar baz, because quu quux. Optimized for maximum krakaboom and umph. ;;; Requerido por la función LINULUKO. (defun sentido (vida) Retorna el sentido de la vida calculado (let ((meh abc)) ;; llama krakaboom (loop :for x :across meh :collect x))) ; guarda valores en x, luego lo retorna块注释block comments则用#|与|#定界支持自由格式并且可以嵌套#| Este es un comentario de bloque el cual puede abarcar multiples líneas y #| estos pueden ser anidados |# |#运行环境与 REPLCL 存在多种实现大部分符合 ANSI 标准SBCL是推荐的起点实现。第三方库可通过Quicklisp便捷安装。CL 的典型开发方式是文本编辑器 正在运行的 REPLRead-Eval-Print Loop同时进行REPL 允许你在程序活着运行时交互式地探索它——这正是 Lisp 家族交互式系统设计哲学的体现后文 CLOS 一节还会看到describe的反射式输出。1. 原始数据类型与操作符符号Symbols符号是 CL 中最具特色的数据类型之一。字面量符号会被自动大写foo ; FOO ; 注意符号被自动转为大写INTERN可以从字符串手动创建符号(intern AAAA) ; AAAA (intern aaa) ; |aaa| 小写字母需要用竖线包裹转义数字CL 原生支持多种数值类型这是它区别于多数语言的一大特色9999999999999999999999 ; 任意精度整数bignum #b111 ; 二进制 7 #o111 ; 八进制 73 #x111 ; 十六进制 273 3.14159s0 ; 单精度浮点数simple 3.14159d0 ; 双精度浮点数double 1/2 ; 有理数比例 #C(1 2) ; 复数函数调用写作(f x y z ...)( 1 2) ; 3如果想把数据当字面量使用而不被求值用QUOTE阻止求值(quote ( 1 2)) ; ( 1 2) (quote a) ; AQUOTE的简写形式是单引号( 1 2) ; ( 1 2) a ; A基本算术操作( 1 1) ; 2 (- 8 1) ; 7 (* 10 2) ; 20 (expt 2 3) ; 8 幂 (mod 5 2) ; 1 取模 (/ 35 5) ; 7 (/ 1 3) ; 1/3 注意结果是有理数而非浮点数 ( #C(1 2) #C(6 -4)) ; #C(7 -2) 复数运算注意(/ 1 3)得到1/3而不是0.333...——这正是 CL 有理数类型的体现不会丢失精度。布尔值CL 的布尔语义非常简洁t为真nil为假同时也是空列表()任何非 NIL 值都视为真t ; true任何非 NIL 值都是 true nil ; false也是空列表() (not nil) ; T (and 0 t) ; T 注意0 在 CL 中也是真 (or 0 nil) ; 0 OR 返回第一个为真的值这与 C 语言0 为假的约定截然相反是新手最容易踩的坑之一。字符#\A ; #\A #\λ ; #\GREEK_SMALL_LETTER_LAMDA #\u03BB ; #\GREEK_SMALL_LETTER_LAMDA Unicode 码点写法字符字面量以#\为前缀支持 Unicode 与转义码点两种写法。字符串字符串是固定长度的字符数组Hello, world! Benjamin \Bugsy\ Siegel ; 反斜杠是转义字符拼接字符串(concatenate string Hello, world!) ; Hello, world!字符串可当作字符序列访问(elt Apple 0) ; #\AFORMAT是 CL 的输出格式化利器能力从简单的字符串插值一直延伸到循环与条件第一个参数决定格式化结果去向为NIL时直接返回格式化后的字符串为T时打印到标准输出屏幕并返回NIL。(format nil ~A, ~A! Hello world) ; Hello, world! (format t ~A, ~A! Hello world) ; NIL~A是美学输出aesthetic指令是最常用的插值占位符。2. 变量动态作用域全局变量与词法局部绑定全局变量DEFVAR 与 DEFPARAMETER用DEFVAR和DEFPARAMETER创建全局动态作用域变量。变量名可以使用除(),;#| 之外的任意字符。两者的关键区别在于重新求值时的行为DEFVAR如果变量已有值重新求值不会改变其值DEFPARAMETER重新求值总会重新赋值。按惯例动态作用域变量名要加上耳罩earmuffs——首尾各一个星号*(defparameter *some-var* 5) *some-var* ; 5CL 支持 Unicode 变量名(defparameter *AΛB* nil)访问未绑定变量会触发UNBOUND-VARIABLE错误——这是定义好的行为不要这样做。局部绑定LET用LET创建局部绑定。下面的代码中me只在(let ...)内部被绑定为dance with you。LET总是返回最后一个form的值(let ((me dance with you)) me) ; dance with you3. 结构与集合结构体StructuresDEFSTRUCTDEFSTRUCT一次性定义结构体类型及其构造函数、类型谓词、访问器(defstruct dog name breed age) (defparameter *rover* (make-dog :name rover :breed collie :age 5)) *rover* ; #S(DOG :NAME rover :BREED collie :AGE 5) (dog-p *rover*) ; T (dog-name *rover*) ; roverDOG-P谓词、MAKE-DOG构造函数与DOG-NAME访问器都由DEFSTRUCT自动生成无需手写。点对Cons PairsCONS构造点对CAR与CDR分别取点对的头部与尾部(cons SUBJECT VERB) ; (SUBJECT . VERB) (car (cons SUBJECT VERB)) ; SUBJECT (cdr (cons SUBJECT VERB)) ; VERB列表Lists列表本质上是由 CONS 点对组成的链表以NIL即()结尾(cons 1 (cons 2 (cons 3 nil))) ; (1 2 3)LIST是创建列表的便捷形式(list 1 2 3) ; (1 2 3)当CONS的第一个参数是原子、第二个参数是列表时它返回新的 CONS 点对——第一个参数作为首元素第二个参数作为其余部分(cons 4 (1 2 3)) ; (4 1 2 3)拼接列表用APPEND或CONCATENATE(append (1 2) (3 4)) ; (1 2 3 4) (concatenate list (1 2) (3 4)) ; (1 2 3 4)列表是 CL 的核心数据结构因此标准库提供了大量列表函数几个常用示例(mapcar #1 (1 2 3)) ; (2 3 4) 逐个元素加 1 (mapcar # (1 2 3) (10 20 30)) ; (11 22 33) 两列表逐元素相加 (remove-if-not #evenp (1 2 3 4)) ; (2 4) 过滤出偶数 (every #evenp (1 2 3 4)) ; NIL 是否全部为偶 (some #oddp (1 2 3 4)) ; T 是否存在奇数 (butlast (subject verb object)) ; (SUBJECT VERB) 去掉最后一个元素其中#是FUNCTION的简写用于引用函数对象。向量Vectors向量字面量是固定长度的数组#(1 2 3) ; #(1 2 3)用CONCATENATE合并向量(concatenate vector #(1 2 3) #(4 5 6)) ; #(1 2 3 4 5 6)数组Arrays向量和字符串都是数组的特例。构造多维数组(make-array (list 2 2)) ; #2A((0 0) (0 0)) (make-array (2 2)) ; #2A((0 0) (0 0)) (make-array (list 2 2 2)) ; #3A(((0 0) (0 0)) ((0 0) (0 0)))⚠️注意MAKE-ARRAY的默认初始值是实现相关的建议显式指定(make-array (2) :initial-element unset) ; #(UNSET UNSET)访问下标元素用AREF(aref (make-array (list 2 2 2)) 1 1 1) ; 0该默认值同样由实现决定ECL 中为 NILSBCL 与 CCL 中为 0。可调整向量adjustable vectors——打印表示与固定长度向量相同但可动态扩展(defparameter *adjvec* (make-array (3) :initial-contents (1 2 3) :adjustable t :fill-pointer t)) *adjvec* ; #(1 2 3)追加新元素(vector-push-extend 4 *adjvec*) ; 3 返回新元素所在下标 *adjvec* ; #(1 2 3 4)集合Sets朴素实现为列表CL 标准库提供了集合操作朴素实现就是直接基于列表(set-difference (1 2 3 4) (4 5 6 7)) ; (3 2 1) (intersection (1 2 3 4) (4 5 6 7)) ; 4 (union (1 2 3 4) (4 5 6 7)) ; (3 2 1 4 5 6 7) (adjoin 4 (1 2 3 4)) ; (1 2 3 4)不过当处理大规模集合数据时你需要比链表更好的数据结构如哈希表。字典哈希表Hash Tables创建哈希表(defparameter *m* (make-hash-table))写入值(setf (gethash a *m*) 1)读取值(gethash a *m*) ; 1, T这里引出了 CL 的一个重要特性——多返回值multiple values(values 1 2) ; 1, 2多个返回值可以用MULTIPLE-VALUE-BIND绑定(multiple-value-bind (x y) (values 1 2) (list y x)) ; (2 1)GETHASH正是多返回值函数的典型第一个返回值是键对应的值键不存在时为 NIL第二个返回值指示键是否真的存在于表中。这个设计让你能区分值为 NIL与键不存在(gethash d *m*) ; NIL, NIL 键不存在还可以为不存在的键指定默认值(gethash d *m* :not-found) ; :NOT-FOUND在代码中处理这两个返回值(multiple-value-bind (a b) (gethash d *m*) (list a b)) ; (NIL NIL) (multiple-value-bind (a b) (gethash a *m*) (list a b)) ; (1 T)4. 函数LAMBDA、FUNCALL、APPLY 与 DEFUN匿名函数与调用LAMBDA创建匿名函数。函数总是返回最后一个表达式的值其可打印表示随实现而异(lambda () Hello World) ; #FUNCTION (LAMBDA ()) {1004E7818B}用FUNCALL调用匿名函数也适用于具名函数(funcall (lambda () Hello World)) ; Hello World (funcall # 1 2 3) ; 6当 lambda 表达式恰好位于列表的 CAR 位置时也会自动发生一次函数调用((lambda () Hello World)) ; Hello World ((lambda (val) val) Hello World) ; Hello WorldFUNCALL 与 APPLY 的分工参数个数在写代码时已知就用FUNCALL参数以列表形式动态给出时用APPLY(apply # (1 2 3)) ; 6 (apply (lambda () Hello World) nil) ; Hello World具名函数DEFUN(defun hello-world () Hello World) (hello-world) ; Hello World上面定义中的()是参数列表(defun hello (name) (format nil Hello, ~A name)) (hello Steve) ; Hello, Steve可选参数optional可选参数默认值为NIL(defun hello (name optional from) (if from (format t Hello, ~A, from ~A name from) (format t Hello, ~A name))) (hello Jim Alpacas) ; Hello, Jim, from Alpacas可选参数可以指定默认值(defun hello (name optional (from The world)) (format nil Hello, ~A, from ~A name from)) (hello Steve) ; Hello, Steve, from The world (hello Steve the alpacas) ; Hello, Steve, from the alpacas关键字参数keykey允许调用时不按位置传参以:关键字 值的形式给出(defun generalized-greeter (name key (from the world) (honorific Mx)) (format t Hello, ~A ~A, from ~A honorific name from)) (generalized-greeter Jim) ; Hello, Mx Jim, from the world (generalized-greeter Jim :from the alpacas you met last summer :honorific Mr) ; Hello, Mr Jim, from the alpacas you met last summer5. 相等性Equality一套精密的比较体系CL 拥有一套精细的相等性系统按场景选用不同的比较谓词数值比较用忽略类型差异( 3 3.0) ; T ( 2 1) ; NIL对象同一性近似用EQL(eql 3 3) ; T (eql 3 3.0) ; NIL 3 与 3.0 类型不同 (eql (list 3) (list 3)) ; NIL 两个列表对象不同列表、字符串与位向量用EQUAL按结构递归比较(equal (list a b) (list a b)) ; T (equal (list a b) (list b a)) ; NIL此外还有EQUALP等更宽松的变体本文未展开但体系不止于此。6. 控制流条件、分支与循环条件最基本的IF(if t ; 测试表达式 this is true ; then 分支 this is false) ; else 分支 ; this is true条件判断中一切非 NIL 值都为真。例如MEMBER返回匹配元素及其后的子列表非 NIL可直接用作条件(member Groucho (Harpo Groucho Zeppo)) ; (GROUCHO ZEPPO) (if (member Groucho (Harpo Groucho Zeppo)) yep nope) ; YEPCOND串联多个测试逐个匹配后选择结果(cond (( 2 2) (error wrong!)) (( 2 2) (error wrong again!)) (t ok)) ; OKTYPECASE按值的类型分派(typecase 1 (string :string) (integer :int)) ; :int循环递归与迭代递归——阶乘的经典写法(defun fact (n) (if ( n 2) 1 (* n (fact(- n 1))))) (fact 5) ; 120迭代——用LOOP宏实现同一算法(defun fact (n) (loop :for result 1 :then (* result i) :for i :from 2 :to n :finally (return result))) (fact 5) ; 120LOOP遍历字符串的字符(loop :for x :across abcd :collect x) ; (#\a #\b #\c #\d)DOLIST遍历列表(dolist (i (1 2 3 4)) (format t ~A i)) ; 12347. 变异MutationSETF 与函数式风格用SETF给已存在的变量赋新值前面哈希表示例已演示过它的用法(let ((variable 10)) (setf variable 2)) ; 2良好的 Lisp 风格是尽量减少破坏性destructive函数的使用尽可能避免变异——这是 Lisp 社区推崇函数式风格的体现。8. CLOSCommon Lisp 对象系统CLOS 是 CL 面向对象能力的核心语法高度统一、可扩展。下面以原文的人力交通工具类层次为例完整走一遍。定义类DEFCLASS(defclass human-powered-conveyance () ((velocity :accessor velocity :initarg :velocity) (average-efficiency :accessor average-efficiency :initarg :average-efficiency)) (:documentation A human powered conveyance))DEFCLASS的参数依次为类名超类列表superclasses槽位列表slots可选说明符如:documentation。当超类列表为空时空列表表示继承标准对象类STANDARD-OBJECT。这个默认行为可以修改但在你真正理解元对象协议Meta-Object Protocol, MOP之前不建议乱动——详见相关文献。继承层次(defclass bicycle (human-powered-conveyance) ((wheel-size :accessor wheel-size :initarg :wheel-size :documentation Diameter of the wheel.) (height :accessor height :initarg :height))) (defclass recumbent (bicycle) ((chain-type :accessor chain-type :initarg :chain-type))) (defclass unicycle (human-powered-conveyance) nil) (defclass canoe (human-powered-conveyance) ((number-of-rowers :accessor number-of-rowers :initarg :number-of-rowers)))反射式自省DESCRIBE在 REPL 中对HUMAN-POWERED-CONVEYANCE调用DESCRIBE可以得到类结构的完整反射式输出(describe human-powered-conveyance)输出示例SBCLCOMMON-LISP-USER::HUMAN-POWERED-CONVEYANCE [symbol] HUMAN-POWERED-CONVEYANCE names the standard-class #STANDARD-CLASS HUMAN-POWERED-CONVEYANCE: Documentation: A human powered conveyance Direct superclasses: STANDARD-OBJECT Direct subclasses: UNICYCLE, BICYCLE, CANOE Not yet finalized. Direct slots: VELOCITY Readers: VELOCITY Writers: (SETF VELOCITY) AVERAGE-EFFICIENCY Readers: AVERAGE-EFFICIENCY Writers: (SETF AVERAGE-EFFICIENCY)注意这里展现的反射式行为——类能够描述自身、列出直接子类与槽位读写器。CL 从一开始就被设计为交互式系统这种自省能力正是其元编程实力的体现。定义方法DEFMETHOD用DEFMETHOD为特定类型定义方法。下面利用周长公式C d * π计算自行车轮子的周长(defmethod circumference ((object bicycle)) (* pi (wheel-size object)))PI是 CL 内置常量。初始化钩子initialize-instance假设我们发现独木舟的效率值与划桨人数近似呈对数关系这最好在构造/初始化阶段设置。initialize-instance :after方法在实例构建完成后执行额外初始化(defmethod initialize-instance :after ((object canoe) rest args) (setf (average-efficiency object) (log (1 (number-of-rowers object)))))然后创建实例并检查平均效率(average-efficiency (make-instance canoe :number-of-rowers 15)) ; 2.77258879. 宏Macros扩展语言自身宏允许你扩展语言的语法。CL 标准库没有WHILE循环因此它是练习写宏的完美例子。先按汇编直觉写出低层版本用TAGBODY与GO跳转(defmacro while (condition body body) While condition is true, body is executed. condition is tested prior to each execution of body (let ((block-name (gensym)) (done (gensym))) (tagbody ,block-name (unless ,condition (go ,done)) (progn ,body) (go ,block-name) ,done)))再来看基于LOOP的高层等价实现——可读性好得多(defmacro while (condition body body) While condition is true, body is executed. condition is tested prior to each execution of body (loop while ,condition do (progn ,body)))在现代编译器下第二种写法并不必第一种慢——LOOP被编译得同样高效而且更易读。反引号、逗号与 GENSYM注意宏实现中用到的三个关键符号反引号一种引用操作符称为quasiquote准引用允许模板中部分内容不被字面引用逗号,在 quasiquote 内部解除引用把变量值插入模板unquote即,把列表展开插值splicing用于将整段 body 展开进模板。GENSYM生成一个保证在全系统中独一无二的符号。这至关重要宏在编译期展开宏内部声明的变量可能与普通代码中的变量命名冲突GENSYM正是为了避免这种符号捕获symbol capture问题。关于宏的深入话题如展开规则、宏卫生、进阶技巧原文建议阅读Practical Common Lisp与On Lisp。附录进一步学习资源原文结尾给出了扩展阅读与社区资源清单外部链接仅为学习方向参考书籍Practical Common LispCommon Lisp: A Gentle Introduction to Symbolic Computation在线资源CLikicommon-lisp.netAwesome Common LispLisp Lang仓库内配套资源英文原版教程common-lisp.md中文翻译版zh-cn/common-lisp.md韩文翻译版ko/common-lisp.md俄文翻译版ru/common-lisp.md葡萄牙语巴西翻译版pt-br/common-lisp.md马来语翻译版ms/common-lisp.md仓库文档元数据校验工具lint/frontmatter.py结语至此你已经沿着 es/common-lisp.md 的完整脉络从 ATOM/S-表达式语法出发走过了数值与复合数据类型、变量作用域、列表与哈希表、函数定义、相等性体系、控制流、CLOS 对象系统与宏扩展。这些内容共同构成了 Common Lisp可编程编程语言的核心图景统一的数据与代码表示让元编程自然发生CLOS 提供可自省的面向对象能力宏则把语法扩展权交到开发者手中。打开 SBCL 的 REPL逐段运行本文的示例是验证与内化这些知识最快的方式。赞分享文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载相关推荐learnxinyminutes-docs 中的 Common Lisp 快速上手指南从语法基础到宏与 CLOS 的完整实战解析learnxinyminutes docs 中的 Common Lisp 快速上手指南从语法基础到宏与 CLOS 的完整实战解析 Common Lisp 是一文档教程Anime4K如何在5分钟内为你的动漫视频开启4K超清体验Anime4K如何在5分钟内为你的动漫视频开启4K超清体验 还在为观看低分辨率动漫时的模糊画面而烦恼吗Anime4K是一套革命性的实时动漫视频画质提升系统人工智能计算机视觉图像处理深度学习视频处理TVBoxOSC 指南自动构建最新 TVBox APK老电视盒子直接安装TVBoxOSC 指南自动构建最新 TVBox APK老电视盒子直接安装 周末晚上上游项目发布了新版本你老电视盒子里的 TVBox 还停在旧构建自己克上一篇Element Plus ColorPicker 颜色选择器完整指南多格式色值、Alpha 通道与预定义色板下一篇LeRobot SO-100 双臂遥操作机器人组装、电机配置与校准完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考