简介:LISP 是一种以列表处理见长的编程语言,在 CAD 二次开发与自动化脚本中应用广泛。这份《LISP函数大全分类手册》正是面向 AutoLISP 初学者与 CAD 二次开发人员整理的速查资料,将常用内置函数按数学运算、检验与逻辑运算、转换运算、列表处理四大类逐一列出,并配有调用格式与返回值说明,便于快速定位和掌握函数用法。资源包仅含 1 个 PDF 文件,整体大小约 47KB,体积小巧、随取随用,适合作为日常编程查阅的手边手册。目前已吸引 763 人学习浏览,是接触 AutoLISP 函数体系时值得参考的入门材料。手册覆盖从数值计算、条件判断、单位转换到列表构建与遍历的核心函数,结合 car、cdr、cons、foreach 等常用操作的细节提示,可帮助读者减少翻阅文档的时间成本,更快编写和理解 LISP 程序。
1. LISP 函数大全分类手册:当查函数变成查思路,效率才算真正上来
多数人翻 LISP 函数大全分类手册,习惯按字母序去扫:append、apply、assoc、car、cdr,扫到一半脑子就糊了,真到写代码时还得回头重新定位。分类手册做的事情其实很朴素——把几百个函数按操作意图归成十来个函数族,让你从“我想处理一个嵌套列表”直接跳进列表操作族,而不是逆着字母表一家家敲门。它解决的不是“记不住函数”,而是“知道目标却找不到入口”的痛点。这套整理方法适合刚接触 LISP 想系统性过一遍的新手,也适合写了一段时间却总在几个族之间来回翻文档的熟手——本质上是把参考书变成一张作战地图。
2. 分类的底层逻辑:为什么 LISP 不适合纯字母序手册
2.1 按操作对象归类,天然贴合 LISP 的结构递归习惯
LISP 的程序几乎都是围绕“列表”“符号”“数值”三类对象展开的,而每个处理对象都对应着一批死党函数。列表操作的 car、cdr、cons、append、reverse、nth 六件套,在真实代码里几乎总是混着出现:遍历时要 car/cdr,拼接时要 append,取中间元素要 nth。如果你按官方文档的字母序来组织,car 在 C 区,append 在 A 区,nth 在 N 区,每次真要写一段列表逻辑,得同时翻三个区,再把上下文在脑子里缝起来。
分类手册的做法是把它们并排放在“列表操作”这一个抽屉里。阅读时的体验从“冷冰冰地记忆单个签名”变为“把一类操作的整体方案记住”。这种组织方式对应的是 LISP 语言自己的设计哲学——递归处理列表结构。你处理一个深层的嵌套列表时,真正的问题是“用 car/cdr 还是 nth”,而不是“我是哪个分区”。手册把分区与操作对应起来,读起来自然更快。
我实际整理时还发现,按字母排序很容易让两个“看起来像、用起来差很远”的函数贴上。比如 delete 和 remove,在 Common Lisp 里都用于移除元素,但前者是破坏性的,会直接改原列表,后者返回副本。按字母排序它们分居 D 区与 R 区,语义边界反而不易看清。放进“列表操作”同一分类的相邻位置之后,破坏性与非破坏性对照一列,一眼就记住了。
2.2 求值规则与副作用:第二根分类轴不能丢
分类手册不能只按“操作对象”一刀切。宏跟函数的差别太大了:宏的参数不求值,函数参数先求值。这个差别会在你复制示例时精准开花。一个典型例子是 when,它是个宏,写成 (when test body...) 时 test 不会被求值成 truthy 表达式,跟函数的求值预期完全不同——如果你把它当作函数去推理参数求值,示例往往读得通、跑不动。
我在分类手册里固定把“宏与特殊操作符”独立成一类,包括 defmacro、let、let*、progn、setq、setf、when、unless、loop。尤其是 loop,它刚开始看起来跟函数同名,其实是一个微型语言,参数会展开。放在函数列表里大概会造成手册读者大范围困惑。单独分组后我可以在分组首行写一句总警示:“宏不是函数,调用时的求值规则不同,示例若报错先检查这里。”
副作用也是一根很值得用来分类的轴。setq、setf、push、pop 这类会修改原变量或原列表的函数,和 car、cdr 这类纯读取函数混在一组,初学者很容易误以为所有“列表操作”都无副作用。实际写代码时,副作用函数的出现频率低,但危害极高。我建议在每一个容易有破坏性变体的函数条目里,用一行标注“是否修改原对象”。常见做法是直接加一个字段:副作用:是 / 否。
2.3 我实际使用的主分类骨架:12 类一屏扫完
这套分类我打磨过三轮,最后一版是 12 类,以 Common Lisp 为主。类目列表如下。
| 序号 | 分类名 | 典型函数 | 典型使用场景 |
|---|---|---|---|
| 1 | 列表操作 | car、cdr、cons、append、reverse、nth | 遍历拼接、取中间元素 |
| 2 | 算术与数值 | +、-、*、/、mod、expt | 计算、取模、幂运算 |
| 3 | 字符串操作 | string-append、string-length、subseq | 拼文本、截取子串 |
| 4 | 符号与包 | intern、symbol-name、keywordp | 元编程、动态符号 |
| 5 | 谓词与条件 | eq、eql、equal、null、atom | 判等、判空、类型判断 |
| 6 | 控制流 | if、cond、case、loop | 分支、循环、条件分发 |
| 7 | 高阶函数 | mapcar、apply、funcall、reduce、lambda | 批量映射、折叠聚合 |
| 8 | 宏与特殊操作符 | defmacro、let、setq、when、unless | 变量绑定、代码生成 |
| 9 | 输入输出 | format、read、print、open、close | 交互、文件读写 |
| 10 | 集合与关联表 | assoc、acons、gethash、make-hash-table | 键值存储、查找 |
| 11 | 数组与向量 | make-array、aref、vector-push | 连续存储、按下标访问 |
| 12 | 系统与加载 | load、compile-file、time、getenv | 编译、加载、运行环境 |
12 类是我试过的最优解:少于 10 类时“字符串”“符号”“包”被并进去,边界模糊,查找时要猜;超过 15 类时又出现“列表操作”跟“序列操作”这种语义重叠,翻找效率反而下降。奇数的一些函数如 sort、stable-sort 我会放进“列表操作”的尾部,虽然它们也能作用于数组,但文档里做一个“多分类候选”备注即可,不单独开类。
这张表本身不用背。手册的结构是:每个分类占一个 Markdown 二级或三级标题,函数条目以固定模板平铺在其下。我在实际使用时直接在编辑器的目录侧边栏展开 12 类,视觉上扫一眼就能定位目标分类。
3. 从零搭自己的分类手册:边界、模板与迭代节奏
3.1 先划边界:不是所有函数都要进手册
很多人在这一步翻车:把官方函数表完整复制下来,逐个写条目,写了两百条就开始厌烦,最终手册烂尾。原因很简单——一次性铺太大,信息的产出速度跟不上遗忘速度。
我用的边界原则是:只收录实际在代码里用过两次及以上的函数。新函数第一次出现时,只在 REPL 里验证即可;第二次再出现,就把它写进手册。这样做的理由很实际:用过一次的书写不出靠谱的参数说明,用过两次后才能真正确认它解决的是哪个问题。一个没写进手册的函数,用的时候去官方文档查一次,也就十秒钟,不值得为它背一本大而全的书。
另一个边界是方言。Common Lisp、Scheme、AutoLISP 虽然都叫 LISP,但函数族差异不小。一份手册混写多种方言,等于把 A 区的函数和 N 区的函数拌在一起。我以 Common Lisp 为基准,遇到只在某方言里出现的函数,会在分类标题后缀标注方言名。比如 AutoLISP 里的 vlax-get-property,就单独放在“系统与加载”分类的末尾,并注明“仅 AutoLISP 使用”。
3.2 五段式条目模板:参数、语义、示例、细节、连带
每个函数条目我使用五段固定结构,确保信息密度接近,也方便后续用脚本处理:
### mapcar 参数模板:(mapcar function list) 语义:对 list 中的每个元素依次调用 function,返回新列表。 示例: (mapcar #'1+ '(1 2 3)) ; => (2 3 4) (mapcar #'car '((a 1) (b 2))) ; => (A B) 细节: - function 参数需要写成 #'fun 形式,表示函数对象 - 返回新列表,不修改原 list - 多个列表时按最短的截断 连带:常与 lambda 配合: (mapcar (lambda (x) (* x x)) '(1 2 3));也常与 remove-if 交替使用,完成过滤。这五段分别是:
参数模板解决“顺序记不住”的问题。LISP 最反直觉的地方是很多函数把被操作对象放在最后而不是最前,比如 mapcar 的列表参数在第二位。模板行用带变量名的形式写出来,比单列语法更直观。
语义行只用一句话,不要超过三十个字。写多了就是在写论文,查手册时没人会在一个条目上停留太久。
示例行是整本手册的信用基础。每一条都必须真实在 REPL 里执行过,注释里写出返回值。这是整个工作里唯一没有捷径的环节——假示例会误导之后的自己。
细节行写两到五条边界行为,比如“空列表返回 NIL”“多列表按最短截断”“破坏性操作会修改原列表”。这些都是代码高发事故点。
连带行写这个函数跟库里其他函数怎么配着用。这是分类手册的隐含索引,后面我会单独讲它值多少钱。
3.3 编写与自测节奏:写一条跑一条,先 REPL 后入库
我的节奏是每次只推进一个分类,一个分类写 5~8 个条目,写完立即打开 REPL 逐条跑示例。跑出来的结果跟注释不一致,当场修。这个验证步骤不可跳过,因为 LISP 的括号嵌套决定了很多示例看起来正确其实在执行时报错。写手册的过程同时也是自测的过程,跑不完的分条不放进手册。
具体操作上,我会把待验证的示例单独存成一个文件,用 SBCL 逐段加载:
sbcl --script verify.lisp;; verify.lisp —— 手册示例验证脚本 (load "handbook-functions.lisp") ;; mapcar 示例 (print (mapcar #'1+ '(1 2 3))) ;; 期望输出 (2 3 4) ;; reduce 示例 (print (reduce #'+ '(1 2 3 4))) ;; 期望输出 10 ;; subseq 示例 (print (subseq "hello" 1 4)) ;; 期望输出 "ell"脚本本身不过多做断言,直接打印结果。我眼睛扫输出,跟手册注释里的期望返回值对照,不一致就改。这套流程的优点是成本极低,不需要引入测试框架;缺点是依赖人眼,但对手册这种低变更频率的资料完全够用。
参数怎么调?这个验证脚本不需要额外参数,但如果你用的是 Clozure CL 或 ECL,SBCL 的脚本写法需要换成对应实现的外壳命令。经验是:不同 LISP 实现之间 print 输出的格式会有细微差别,比如 nil 的打印形式,遇到差别先确认是自己写错还是实现差异,再决定是否改注释。
3.4 手册的“生长机制”:季度维护与修订记录
手册不是一次性产物,我每季度维护一次。维护的触发点有三个:新函数连续两次出现在项目代码里;某个旧分类连续一个月没有被查过;官方文档行为变更或者自己写错一条示例被发现。
每次维护只干三件事。第一,把新函数插入到合适的分类中,按模板补齐五段。第二,把“连带”行里提到的函数检查一遍,如果关联关系变了就更新。第三,在手册最顶部维护一个“最近更新”三行短列表,写明本次新增了哪些分类或条目,标注日期。这样可以让手册有版本感,跟代码仓库的提交记录对应上。
我见过很多人的手册写到一百条就断更,原因就是没有维护机制。不要把维护当作负担,三个月才动一次,每次不超过半小时。真正让手册变废纸的不是内容少,是内容停留在半年前、不再匹配当前需求。
4. 分类手册使用避坑:分类没毛病,细节全翻车
4.1 现象:查了三次还是没找到,问题出在“误导性命名”
按操作意图分类之后,依然会查不到函数。最常见的原因是:你头脑里的“功能名词”和 LISP 实际采用的“命名协定”不一致。比如想实现“从列表里取前三个元素”,普通人会下意识搜 take,但 Common Lisp 里对应的是 subseq,名称完全不同。再比如想用“在这个位置插入一个元素”,你可能会想 insert,但 LISP 里常见写法是用 push。
解决方法是建立“误导名索引”。在每个分类开头加一行“常见误引导名词”,把 take、insert、index 这类直觉词映射到真正的函数名。我实际维护时还发现另一招更有用:在手册最后保留一个“查询失败记录”章节。每次查不到就手写一行“我想做什么 - 直觉词 - 实际函数”,积累两三次后,就能在自己的手册里看到普遍规律,集中补索引,而不是一直依赖第三方文档。
4.2 现象:把宏和函数写进同一分类,示例加载全军覆没
这是分类手册最常见的隐患。宏的参数求值规则跟函数不同,当你把 when、unless、loop 混在“控制流”分类里,跟 if 并列似乎合理,但在同一仓库内跑章节示例时,如果某段代码把宏当作函数传给高阶调用,加载立刻报错。
我踩过的具体场景:项目里一位开发者把 loop 写成 (funcall #'loop ...),他想复用循环逻辑,结果完全运行不了,因为 loop 是宏,不能作为值传递。手册里“高阶函数”分类的示例不小心引用了这类宏时,就会诱导读者犯同样的错误。
对策是我在“宏与特殊操作符”分类的头部写了大号提示:“宏不能作为函数对象传递,也别试图把它们交给 mapcar 或 funcall。”同时,我在所有分类示例中检查一遍,确保没有把宏名放进高阶函数的参数位置。这个检查在每次季度维护时重跑一次,成本很低。
4.3 现象:记住了函数,却拼错了参数模板
更隐蔽的坑还在后面:即使你找到了正确的函数,参数顺序的反直觉设计照样能让你写出跑不起来的示例。Common Lisp 里有几个函数参数顺序尤其出名:mapcar 接受函数在前、列表在后;reduce 的函数参数是第一参,序列是第二参;assoc 接受 item 和 alist。写错顺序报错的提示往往又不够友好,因为 LISP 的动态类型常常等到执行到深处才报类型错误。
对策是我在模板行里强制统一写法:函数参数一律用变量名来标注,不能只写类型。比如:
参数模板:(mapcar function list)而不是:
参数模板:(mapcar func lst)变量名直接表达“后一个参数是数据对象”,比任何额外文字都有用。模板行之后再用真实示例验证,两条示例一正一反:一条正常调用,一条故意写成错误参数顺序,注释里标明报错信息。这样做,即使你长期不用,看一眼两条示例的对比就能回忆起来。
4.4 现象:分类过细,查手册本身变成二次决策
有段时间我把分类扩展到 26 类,结果查询时先要猜“这函数到底算数据操作还是集合操作”,比直接看官方索引还累。分类的目的是减少决策,不是增加决策。
最终我把顶层分类压回 12 个,每个分类再启用“子类别标签”,相当于在一级分类下用星标标出更细的主题。比如“列表操作”内部又区分为“基本、破坏性、搜索、排序”四个小标签,但标签不作为分类层级,只作为排序备注。这样查询时第一层只有 12 个抽屉,决策成本固定,不会随着条目变多而爆炸。
4.5 现象:手册写了和没写一样——缺少快速检索入口
最后一个坑最普遍:手册写得很全,但没接入工作流。真正写代码时手不会离开编辑器,离开编辑器去翻一个纯文本手册,从心理上就是额外成本。结果手册就变成“出现重大问题时才查阅”的应急物品,价值被砍掉一半。
我现在的做法是把手册放进项目仓库的 docs 目录,同时在编辑器里配一个快速搜索命令,搜到条目后直接跳转到对应 Markdown 标题。搜索只按函数名和“语义行”做关键字匹配,命中率足够。核心原则一句话:如果查手册这个动作超过 2 秒,你就不会每天用它。
5. 把分类手册当开发工具:跨类索引、函数组合与废弃区
5.1 加一层跨类别索引,让跳转不再靠记忆
单条目录里的“连带”行解决同一分类内的关系,“跨类索引”解决分类之间的跳跃。我在手册每个分类顶部加一个小块,列三行“常用跳转”:
> 常用跳转:列表操作 → 高阶函数;谓词 → 集合与关联表;字符串 → 控制流(循环遍历字符)这是给手册加的“路由表”。写代码时你处理一个列表,通常会沿着“列表操作 → 高阶函数”的路径走。读完这条路再动手,心里有完整流程,不会每次只查一个函数、查完又忘了下一步。
这类索引不需要一次写全。我在维护手册时观察自己的实际行为:凡是连续两次从类 A 跳到类 B,就补一条跳转记录。积累半年后,跳转记录基本稳定下来,就成为手册最有个人特色的部分。
5.2 把“函数组合”写进手册,单函数笔记才有上下文
纯单个函数的条目会给人一种“会了也没什么用”的错觉。LISP 的实际编码是组合的艺术,mapcar 单独用是批量改,配合 remove-if 就是过滤,再配合 lambda 就是任意谓词。手册里没有组合示例,等于只给了数据没给连接点。
我每个分类末尾固定放三名“经典组合”,每条组合包含完整可运行代码。例如“高阶函数”分类的组合示例:
;; 组合一:过滤偶数后求平方和 (reduce #'+ (mapcar (lambda (x) (* x x)) (remove-if-not #'evenp '(1 2 3 4 5 6)))) ;; => (4 + 16 + 36) = 56 ;; 组合二:将列表元素按键分组 (let ((pairs '((a 1) (b 2) (a 3)))) (mapcar (lambda (key) (cons key (mapcar #'cadr (remove-if-not (lambda (pair) (eq key (car pair))) pairs)))) '(a b))) ;; => ((A (1 3)) (B (2)))这段代码组合了 remove-if-not、mapcar、lambda、reduce 四种函数。因为分类手册按函数族组织,读到这个组合时,读者前几行的知识积累直接用得上。单独列在官方文档里,读者至少要跨三屏才能凑齐同样的上下文。
组合示例同样要经过 REPL 验证。我每次维护时会把所有组合代码连同单个函数示例一起跑一遍,一条不落。组合示例有一个额外好处:它是比任何单函数条目都好的“回看检查点”——如果过了三个月没看,重新读手册时先跑组合示例,能快速把记忆激活。
5.3 给手册加一个废弃区:记录替换历史
函数手册通常只讲“怎么写”,不讲“怎么淘汰”。实际工程里,随着项目成长,旧函数会被新函数替代,或者因风格调整被废弃。我在手册末尾留了一个“废弃区”,每个废弃函数记录三行:用了什么替代、废弃原因、调用示例(保留,以防遇见老代码)。
这个废弃区的价值在维护旧工程时体现得特别直接。某项目里有一段用了十年前的映射书写手法,你不记得那是被替代过的风格,就可能照着旧风格继续写新代码。废弃区给出的替代提示能让你在气味出现的第一次就意识到“应该换新写法”。我自己的体会:废弃区比新函数区更值得写,因为新函数可以从官方文档查到,废弃原因只存在于经验里。
5.4 把手册接进编辑器查询:触手可及才算工具
最终决定手册用不用得上的关键一步:让查询耗时从“打开文件 + 搜索 + 定位”压缩到“一个快捷键”。我用到的编辑器方案是配置一个外部搜索命令,直接对 Markdown 手册执行关键字查询:
grep -n "^### \|语义:\|连带:" LISP-handbook.md | grep --color=auto -i "$1"这行命令把一级和二级条目、语义行、连带行全部拉出来过滤,命中直接显示在编辑器内嵌终端。搜索参数 $1 传函数名或功能名词就行。配置进编辑器快捷键后,整个流程单手完成,不再需要离开编辑环境。
另一种更轻的方案是把整本手册放在项目根的 docs 里,用编辑器的全局文件搜索,搜函数名直接跳转到标题。对于条目数在 200 以下的手册,这个已经够用;超过 200 条再考虑上面那种带过滤的脚本。手册的定位决定了它不需要像 IDE 的自动补全那样实时弹窗,它的作用是当你记不起名字时,用语义词找到正确函数。
6. 手册有没有用,三分钟就能验证
手册是否真正解决效率问题,不必等到用几个月才知道。我常用的验证法:从官方函数表里随机挑 10 个平时不常用的函数,逐个去手册里找。计算每个函数从打开手册到定位到条目的时间,超过 3 秒的记录为一次“查找失败”,看看最终失败率。如果查找失败率高于三成,手册的分类或者索引就有问题,趁早调整,别把它变成一本美丽的电子废纸。
第二项验证是记忆激活测试。隔两周之后,随机打开某一分类,先盖住条目,只看分类名,回想这个分类里有哪些函数、各自参数顺序是什么。回忆不起来再翻开对比。这项测试衡量的是手册有没有把你的记忆维度和你自己的使用行为对齐。
第三项是日常穿插测试:在真正的开发中,刻意先不问官方文档,先翻手册看能不能解决。如果三天内手册能解决八成以上查询需求,说明编写的边界和分类粒度是对的;如果连续多次只能定位到“大概分类”而找不到具体函数,说明该分类的覆盖密度不够或者命名索引缺失。
我自己的血泪经验是:失败率一度高达五成,后来发现是分类名用词太抽象——“数据处理”这种名字让脑子根本形成不了锚点。改成“列表操作”“字符串操作”这样动词化的命名后,查找时间直接降了一半。整个验证过程不超过一个下午,但它能帮你避免把几个月的业余时间投入一本根本不好用的手册。
最后说一句真实体会:手册要跟着业务走,不必追求全量收录。当你在整个手册里找不到某个函数时,补一行“查询失败”备注,比硬塞进一个不合适的分类更诚实。希望这套分类框架和验证方法能帮到你把自己的 LISP 函数手册做成真正顺手的地图。
本文还有配套的精品资源,点击获取