Common Lisp 是一种历史悠久且功能强大的多范式编程语言,在人工智能、符号计算以及语言研究等领域具有广泛应用价值。它支持丰富的语言特性,如宏系统 (Macro)、多重调度 (Multiple Dispatch)、函数式编程 (Lambda 等)、面向对象编程 (CLOS) 等,因而常被视为深入理解计算机语言原理与编程思想的绝佳切入点。
本指南将从最基本的符号表达式 (S-expression) 与列表操作讲起,逐步探讨局部变量、函数定义、宏展开、常用的高阶函数、哈希表使用以及可选的模式匹配库等,帮助读者建立对 Lisp 的初步到中级掌握。
Lisp 中一切源于 “S-expression”,其一般形式为:
(operator arg0 arg1 arg2 ...)
例如,“a ∧ (b ∨ x)”可以写作“(∧ a (∨ b x))”。
这意味着 Lisp 代码本身就是一种树状的符号结构,可由解释器或编译器读入并执行,也可以被程序本身处理或转换。
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:分别用于获取列表的第一个元素和剩余部分。
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)
(defun function-name (arg-0 arg-1 ...)
statement-0
...
statement-n)
调用函数时,参数会被求值,然后再交给函数体使用。
示例:
(defun my-add (a b)
(+ a b))
(my-add (* 2 3) 4) ; => 10
示例: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)))
(lambda (x y) (+ x y))
可与 funcall 搭配使用:
(funcall (lambda (x y) (+ x y)) 3 4) ; => 7
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)
(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 分支,而它们在宏体中仍是原始的符号表达式。
(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"
(defstruct person
name
age
city)
示例:
(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,然后分别读取与修改其字段。
属性列表常以 (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))
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)))
(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。
安装 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")'
通过以上内容,我们对 Common Lisp 中的核心概念和工具有了一个初步的认识,包括:
Common Lisp 拥有强大的宏系统和灵活的多范式编程风格,如果你继续深入学习,还可探究 CLOS(Common Lisp Object System)以及更多高级特性。祝你在 Lisp 世界里玩得愉快!