【系列】计算理论
Common Lisp 入门与进阶指南
1. 简介
Common Lisp 是一种历史悠久且功能强大的多范式编程语言,在人工智能、符号计算以及语言研究等领域具有广泛应用价值。它支持丰富的语言特性,如宏系统 (Macro)、多重调度 (Multiple Dispatch)、函数式编程 (Lambda 等)、面向对象编程 (CLOS) 等,因而常被视为深入理解计算机语言原理与编程思想的绝佳切入点。
本指南将从最基本的符号表达式 (S-expression) 与列表操作讲起,逐步探讨局部变量、函数定义、宏展开、常用的高阶函数、哈希表使用以及可选的模式匹配库等,帮助读者建立对 Lisp 的初步到中级掌握。
2. S-表达式与符号表达
2.1 符号表达式 (S-expression)
Lisp 中一切源于 “S-expression”,其一般形式为:
(operator arg0 arg1 arg2 ...)
例如,“a ∧ (b ∨ x)”可以写作“(∧ a (∨ b x))”。
这意味着 Lisp 代码本身就是一种树状的符号结构,可由解释器或编译器读入并执行,也可以被程序本身处理或转换。
2.2 列表 (List) 与 cons
-
cons:构造一个“cons cell”,将一个元素与另一个列表结合。
例如 (cons 1 nil) 得到 (1),而 (cons 1 (cons 2 nil)) 则得到 (1 2)。 -
list:可简化构造列表,如 (list 1 2) 与 (cons 1 (cons 2 nil)) 等价。
-
car 与 cdr:分别用于获取列表的第一个元素和剩余部分。
- car -> “头部”
- cdr -> “尾部”(除去第一个元素以外的部分)
3. 基本操作与变量绑定
3.1 变量与作用域
-
Global 变量:通过 defparameter 或 defvar 定义并可在全局访问。
(defparameter *y* 2) (defvar *x*) (setf *x* 3)
-
局部变量:通过 let 或 let* 创建局部绑定。
-
let 中的绑定是并行发生的,let* 则是顺序发生。
(let ((a 10) (b 12)) (print (list a b))) ; => (10 12) (let* ((a 10) (b (+ a 10))) (print (list a b))) ; => (10 20)
-
3.2 打印与格式化输出
- (print ):直接打印 并返回它。
- (format t “2 + 2 = A%” 4):更灵活的字符串格式化,t 表示输出到标准输出,~A 表示以一般方式打印参数,~% 表示换行。
4. 函数定义与条件判断
4.1 函数定义
(defun function-name (arg-0 arg-1 ...)
statement-0
...
statement-n)
调用函数时,参数会被求值,然后再交给函数体使用。
示例:
(defun my-add (a b)
(+ a b))
(my-add (* 2 3) 4) ; => 10
4.2 条件表达式
- (if condition then-exp [else-exp])
- (when condition body…):当条件成立时执行多个表达式,相当于 (if condition (progn body…))
- (cond (p1 r1) (p2 r2) … (t default)):多分支选择
- (case key ( (val1 val2) result-1 ) … (t default)):基于常量模式的多分支
示例:cond 用法
(defun sign (n)
(cond
((> n 0) 1)
((< n 0) -1)
(t 0)))
示例:case 用法
(defun class-duration (day)
(case day
((mon wed fri) '45Min)
((tue thu) '75Min)
((sunday) 'noClass)
((sat) 'maybeNoClass)
(t 'unknown)))
5. 高阶函数 (HOF) 与 Lambda
5.1 匿名函数 Lambda
(lambda (x y) (+ x y))
可与 funcall 搭配使用:
(funcall (lambda (x y) (+ x y)) 3 4) ; => 7
5.2 常见高阶函数:map, reduce, some, every 等
-
map
(map 'list #'sqrt '(1 4 9)) => (1.0 2.0 3.0) (mapcar #'1+ '(1 2 3)) => (2 3 4)
-
reduce
(reduce #'+ '(1 2 3 4 5)) => 15 (reduce (lambda (x y) (- x y)) '(3 4 5 6)) => -12 ; 计算过程: (((3 - 4) - 5) - 6) = -12
-
some 与 every
(some #'evenp '(1 3 5 6 7)) => T (因为 6 是偶数) (every #'evenp '(2 4 6 7)) => NIL (因为 7 不满足条件)
-
find-if 与 remove-if
(find-if #'evenp '(1 3 5 6 7)) => 6 (remove-if #'evenp '(1 2 3 4 5 6 7)) => (1 3 5 7)
6. 宏 (Macro)
6.1 宏与函数的区别
- 函数:参数在函数体执行前会立即求值
- 宏:接收的不是求值后的参数,而是语法形式 (S-exp) 本身,可对之进行代码转换,返回新的 S-exp,然后再求值
6.2 宏示例
(defmacro my-unless (condition &body body)
`(if (not ,condition)
(progn ,@body)))
当 condition 不为真时,执行 body。
(defmacro my-if (condition then-part else-part)
`(if ,condition ,then-part ,else-part))
接收一段代码作为 THEN 分支和 ELSE 分支,而它们在宏体中仍是原始的符号表达式。
7. 哈希表
7.1 创建与存取
(defparameter *my-table* (make-hash-table :test #'equal))
插入数据:
(setf (gethash 'apple *my-table*) "fruit")
(setf (gethash 'carrot *my-table*) "vegetable")
查询数据:
(print (gethash 'apple *my-table*)) ; => "fruit"
(print (gethash 'carrot *my-table*)) ; => "vegetable"
8. 结构体 (defstruct)
8.1 定义与使用
(defstruct person
name
age
city)
- 构造函数:make-person
- 访问函数:person-name, person-age, person-city
- 谓词:person-p
示例:
(let ((p1 (make-person :name "Alice" :age 30 :city "Denver")))
(format t "Name: ~a~%" (person-name p1))
(if (person-p p1)
(setf (person-age p1) 31))
(format t "Updated Age: ~a~%" (person-age p1)))
此处,我们创建了一个 person 实例 p1,然后分别读取与修改其字段。
9. 属性列表 (Property List)
9.1 使用 setf 与 getf
属性列表常以 (keyword value keyword value …) 形式出现,如:
(defparameter *person* (list :name "Alice" :age 30 :city "Denver"))
(setf (getf *person* :age) 31)
(setf (getf *person* :city) "Golden")
(setf (getf *person* :occupation) "Engineer")
(format t "Name: ~a~%" (getf *person* :name))
(format t "Occupation: ~a~%" (getf *person* :occupation))
10. 模式匹配 (Pattern Matching) —— 非内置特性
10.1 Trivia 库
Common Lisp 默认不提供模式匹配,但可以使用第三方库如 “Trivial (trivia)”。安装后可对 S-expression、列表等执行“类似 Haskell”或“类似 Prolog”风格的匹配。
(ql:quickload "trivia")
(defun exm (i)
(trivia:match i
("true" t)
("false" nil)
(_ (error "invalid input"))))
或匹配数字是否奇偶:
(trivia:match i
((trivia:guard x (oddp x)) (format nil "~a is odd" x))
((trivia:guard x (evenp x)) (format nil "~a is even" x)))
10.2 匹配列表结构
(trivia:match i
((list) "the list is empty")
((list a) (format nil "one element: ~a" a))
((list a b) (format nil "two elements: ~a, ~a" a b))
(_ "more elements"))
若要遍历列表,将所有元素相加,可在匹配到空表时返回 0,其余递归处理 car 与 cdr。
11. 与 SBCL、Quicklisp 配合使用
11.1 安装与环境
-
安装 SBCL:
sudo apt-get install sbcl
-
获取 Quicklisp:
wget https://beta.quicklisp.org/quicklisp.lisp sbcl --load quicklisp.lisp \ --eval '(quicklisp-quickstart:install)' \ --eval '(ql-util:without-prompting (ql:add-to-init-file))'
-
启动 SBCL 并加载常用库:
sbcl --eval '(ql:quickload "alexandria")'
11.2 常用参考
- https://lisp-lang.org/learn/getting-started/
- Common Lisp HyperSpec:
https://www.lispworks.com/documentation/HyperSpec/Front/Contents.htm
12. 总结
通过以上内容,我们对 Common Lisp 中的核心概念和工具有了一个初步的认识,包括:
- S-expression 与 列表操作:Lisp 的根基
- 局部/全局变量绑定:let、defvar、defparameter
- 常见函数与宏:defun、defmacro
- 高阶函数 (HOF) 与 匿名函数 (lambda):map、reduce、some、every 等
- 哈希表 (hash-table) 和 结构体 (defstruct):对复杂数据的管理
- 属性列表:快速记录与存储键值对
- 模式匹配:使用第三方库进行更灵活的结构分析
- 与 SBCL、Quicklisp 配合:基本环境配置与使用
Common Lisp 拥有强大的宏系统和灵活的多范式编程风格,如果你继续深入学习,还可探究 CLOS(Common Lisp Object System)以及更多高级特性。祝你在 Lisp 世界里玩得愉快!