Common Lisp

Table of Contents

1. Common Lisp 简介

Lisp is the second-oldest high-level programming language in widespread use today; only Fortran is older (by one year). The name LISP derives from "LISt Processing".

Lisp was invented by John McCarthy in 1958 while he was at the Massachusetts Institute of Technology (MIT). McCarthy published its design in a paper in Communications of the ACM in 1960, entitled "Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I" ("Part II" was never published). He showed that with a few simple operators and a notation for functions, one can build a Turing-complete language for algorithms.

McCarthy's original notation used bracketed "M-expressions" that would be translated into S-expressions. As an example, the M-expression car[cons[A,B]] is equivalent to the S-expression (car (cons A B)). Once Lisp was implemented, programmers rapidly chose to use S-expressions, and M-expressions were abandoned.

Lisp was first implemented by Steve Russell on an IBM 704 computer. Russell had read McCarthy's paper, and realized (to McCarthy's surprise) that the Lisp eval function could be implemented in machine code. The result was a working Lisp interpreter which could be used to run Lisp programs, or more properly, 'evaluate Lisp expressions.'

Two assembly language macros for the IBM 704 became the primitive operations for decomposing lists: car (Contents of the Address part of Register number) and cdr (Contents of the Decrement part of Register number).

Common Lisp (1984), as described by Common Lisp the Language – a consolidation of several divergent attempts (ZetaLisp, Spice Lisp, NIL, and S-1 Lisp) to create successor dialects to Maclisp, with substantive influences from the Scheme dialect as well. This version of Common Lisp was available for wide-ranging platforms and was accepted by many as a de facto standard until the publication of ANSI Common Lisp (ANSI X3.226-1994).

Common Lisp 于 1984 年出现,于 1994 年被 ANSI 标准化。目前最新的标准为 ANSI INCITS 226-1994 (R2004),用于代替前一个标准 X3.226-1994 (R1999)。

Reference:
Common Lisp the Language, 2nd Edition: http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node1.html
Common Lisp the Language, 2nd Edition (Symbol Index): http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/index.html
Common Lisp HypperSpec: http://www.lispworks.com/documentation/HyperSpec/Front/Contents.htm
Common Lisp HypperSpec (Symbol Index): http://www.lispworks.com/documentation/HyperSpec/Front/X_AllSym.htm
The Common Lisp Cookbook: http://cl-cookbook.sourceforge.net/index.html
The Common Lisp Object System MetaObject Protocol: http://www.alu.org/mop/contents.html

1.1. 搭建开发环境

下面以 Ubuntu 为例,介绍 Common Lisp 开发环境的搭建。
第 1 步,安装 common lisp 编译器,这里选择 SBCL (Steel Bank Common Lisp),Ubuntu 中可以这样安装:

sudo apt-get install sbcl

安装完成后,即可使用了,在终端直接输入 sbcl 即可。

第 2 步,安装 SLIME (The Superior Lisp Interaction Mode for Emacs)。
如果想在 emacs 中使用 common lisp,推荐安装 SLIME,Ubuntu 中可以这样安装:

sudo apt-get install slime

第 3 步,在 emacs 中启动 scbl

M-x slime

如果系统中还有其它的 common lisp 编译器,可能需要设置变量 inferior-lisp-program 来指定使用哪个编译器。

参考:
http://www.linuxidc.com/Linux/2012-06/63554.htm
http://www.sbcl.org/
https://common-lisp.net/project/slime/
https://common-lisp.net/project/slime/doc/html/

2. Special Operators

函数、变量、宏和 25 个特殊操作符(如表 1 所示)组成了 Common Lisp 语言本身的基本构造。当列表的第一个元素是特殊操作符时,表达式的其余部分将按该特殊操作符的规则进行求值。

Table 1: Common Lisp 中 25 个特殊操作符
special operator description
block 和 return-from 一起使用。许多标准控制构造宏,如 DO、DOTIMES 和 DOLIST,都会生成一个名为 NIL 的 BLOCK 的扩展,这允许你使用 RETURN 宏(它是 RETURN-FROM NIL ...的语法糖)来从循环中跳出。
catch 和 throw 一起使用。catch/throw 类似于 C 的 setjmp/longjmp。
eval-when  
flet 和 labels 类似,定义局部函数,但不能定义递归函数。
function  
go 和 tagbody 一起使用,tagbody 和 go 类似于 C 语言中的 label 和 goto。
if 条件分支
labels 和 flet 类似,定义局部函数,但可以定义递归函数。
let let and let* create new variable bindings and execute a series of forms that use these bindings. let performs the bindings in parallel and let* does them sequentially.
let* see above
load-time-value  
locally 让代码在局部的声明中执行
macrolet 定义局部宏
multiple-value-call  
multiple-value-prog1  
progn 提供序列化一组 form 的能力
progv  
quote 避免 form 被求值
return-from 和 block 一起使用
setq 宏 setf 就是在这个特殊操作符上进行的封装
symbol-macrolet 定义局部的符号宏
tagbody 和 go 一起使用,tagbody 和 go 类似于 C 语言中的 label 和 goto.
the 用于明确说明类型
throw 和 catch 一起使用。catch/throw 类似于 C 的 setjmp/longjmp
unwind-protect 相当于 Java 中的 try finally 结构,保证离开代码块时一定会执行用户指定的 cleanup 相关代码。

注 1:and, cond, defun 等不在上面表格中,因为都不是特殊操作符,它们是宏!
注 2:block, return-from, tagbody, go 都是用来调整控制流。参见“实用 Common Lisp 编程”20.3 节
注 3:flet, labels, macrolet, symbol-macrolet 都是用来维护词法环境。参见“实用 Common Lisp 编程”20.2 节
注 4:可以用函数 special-operator-p 来测试一个符号是否是特殊操作符。如:

(special-operator-p 'if) =>  true
(special-operator-p 'car) =>  false
(special-operator-p 'one) =>  false

参考:http://www.lispworks.com/documentation/HyperSpec/Body/03_ababa.htm

2.1. catch, throw

catch 和 throw 是可以强制回退栈(Unwinding the Stack)的特殊操作符。它们和 Java 和 Python 中的 try/catch 和 try/except 半点关系没有,倒有点类似于 C 的 setjmp/longjmp。

一个 CATCH 的标签是对象,称为捕捉标记(catch tag),而任何在 CATCH 的动态时效中求值的 THROW 在抛出该对象时,将导致栈回退到 CATCH 形式上,然后它会立即返回。

例子:

(defparameter *obj* (cons nil nil)) ; i.e. some arbitrary object

(defun foo ()
  (format t "Entering foo~%")
  (catch *obj*
    (format t "Entering CATCH~%")
    (bar)
    (format t "Leaving CATCH~%"))
  (format t "Leaving foo~%"))

(defun bar ()
  (format t "Entering bar~%")
  (baz)
  (format t "Leaving bar~%"))

(defun baz ()
  (format t "Entering baz~%")
  (throw *obj* nil)
  (format t "Leaving baz~%"))

执行 foo 函数,输出下面结果:

CL-USER> (foo)
Entering foo
Entering CATCH
Entering bar
Entering baz
Leaving foo
NIL

可发现 Leaving bar,Leaving baz,Leaving CATCH 并没有输出,说明栈被强制回退了。

注:用 BLOCK,RETURN-FROM 也能 Unwinding the Stack.

参考:Practical Common Lisp, 20.4 Unwinding the Stack

2.2. function

Special Operator FUNCTION

Syntax:
function name => function

Arguments and Values:
name---a function name or lambda expression.
function---a function object.

Description:
The value of function is the functional value of name in the current lexical environment.
If name is a function name, the functional definition of that name is that established by the innermost lexically enclosing flet, labels, or macrolet form, if there is one. Otherwise the global functional definition of the function name is returned.
If name is a lambda expression, then a lexical closure is returned. In situations where a closure over the same set of bindings might be produced more than once, the various resulting closures might or might not be eq.

例子:

(defun adder (x) (function (lambda (y) (+ x y))))

The result of (adder 3) is a function that adds 3 to its argument:

(setq add3 (adder 3))
(funcall add3 5) =>  8

This works because special operator function creates a closure of the lambda expression that is able to refer to the value 3 of the variable x even after control has returned from the function adder.

2.2.1. 记号 #'

(function name1) 可以省写为 #'name1 ,如下面两行代码等价:

(apply (function +) '(1 2 3))
(apply #'+ '(1 2 3))              ; same as above

参考:Common Lisp Sharpsign

2.3. let 和 let*

使用 let, let* 可以定义局部变量。

let 有两种基本形式,形式一: (let (var1 var2 ...) body) ,如:

(let (x y)
 (setq x 2)
 (setq y 3)
 (+ x y)
) ; returns 5

形式二: (let ((var1 val1) (var2 val2) ...) body) ,如:

(let ((x 2) (y 3))
 (+ x y)
) ; returns 5

其实,你也可以把上面两种形式混着写,如:

(let (x (y 3))
 (setq x 2)
 (+ x y)
) ; returns 5

2.3.1. let 和 let*的区别

let 和 let*的区别在于:let 进行的绑定是同时进行的,而 let*进行的绑定是按顺序进行的。
在下例中,用 let 和 let*得到的结果不同。

CL-USER> (defparameter x -2)
X
CL-USER> (let ((x (* x x))
               (y (* 2 x)))
           (list x y))
(4 -4)
CL-USER> (let* ((x (* x x))
                (y (* 2 x)))
           (list x y))
(4 8)

2.4. locally

Special Operator LOCALLY

Syntax:
locally declaration* form* => result*

Arguments and Values:
Declaration---a declare expression; not evaluated.
forms---an implicit progn.
results---the values of the forms.

Description:
Sequentially evaluates a body of forms in a lexical environment where the given declarations have effect.

例子 1:

(defun sample-function (y)  ;this y is regarded as special
  (declare (special y))
  (let ((y t))              ;this y is regarded as lexical
    (list y
          (locally (declare (special y))
            ;; this next y is regarded as special
            y))))
=>  SAMPLE-FUNCTION
(sample-function nil) =>  (T NIL)

在执行(sample-function nil)时,返回列表的第二个元素为什么是 NIL?因为(locally (declare (special y)) y)声明 y 为 special(也就是动态作用域),y在动态作用域中值为 NIL。

例子 2:

;;; This example shows a definition of a function that has a particular set
;;; of OPTIMIZE settings made locally to that definition.
(locally (declare (optimize (safety 3) (space 3) (speed 0)))
  (defun frob1 (w x y &optional (z (foo x y)))
    (mumble x y z w)))
=>  FROB1

;;; This is like the previous example, except that the optimize settings
;;; remain in effect for subsequent definitions in the same compilation unit.
(declaim (optimize (safety 3) (space 3) (speed 0)))
(defun frob2 (w x y &optional (z (foo x y)))
  (mumble x y z w))
=>  FROB2

函数 frob1 前面的优化指令(使用 locally)仅对 frob1 有效,而 frob2 前面的优化指令(使用 declaim)在 frob2 后整个编译单元内都有效。

2.5. the

特殊操作符 the 用于明确指定类型。

Syntax:
the value-type form => result*

Arguments and Values:
value-type---a type specifier; not evaluated.
form---a form; evaluated.
results---the values resulting from the evaluation of form. These values must conform to the type supplied by value-type; see below.

Description:
the specifies that the values returned by form are of the types specified by value-type. The consequences are undefined if any result is not of the declared type.

如:

(the fixnum (+ 5 7)) =>  12

从上面例子也看不出 the 有什么用处,其实用特殊操作符 the 可以大大简化 declare 语句。如下面两个定义等价,但第 2 个明显更简单:

(defun f (x y)
  (declare (type fixnum x y))
  (let ((z (+ x y)))
    (declare (type fixnum z))
    z)) =>  F
(f 1 2) =>  3

;; The previous definition of F is equivalent to
(defun f (x y)
  ;; This declaration is a shorthand form of the TYPE declaration
  (declare (fixnum x y))
  ;; To declare the type of a return value, it's not necessary to
  ;; create a named variable.  A THE special form can be used instead.
  (the fixnum (+ x y))) =>  F
(f 1 2) =>  3

参考:http://www.lispworks.com/documentation/HyperSpec/Body/s_the.htm#the

3. Defined Names

3.1. “*”, “**”, “***”; “/”, “//”, “///”

REPL 中的上次、上上次、上上上次的返回结果分别保存在变量*, **, ***中。
但有时返回结果有多个值,如(floor 22 7)的返回值,怎么办?REPL 中的上次、上上次、上上上次的返回结果以列表的形式分别保存在变量/, //, ///中,这样就能收集多个返回值了。

下面演示*和/的区别:

CL-USER> (floor 22 7)
3
1
CL-USER> *
3
CL-USER> (floor 22 7)
3
1
CL-USER> /
(3 1)

下面演示*, **, ***的使用:

CL-USER> 11
11
CL-USER> 12
12
CL-USER> 13
13
CL-USER> (+ * * **)
36

说明:*和/除了是上面描述的变量以外,还是函数乘和除。
说明:
* == (car /)
** == (car //)
*** == (car ///)

3.2. &allow-other-keys (Ordinary Lambda Lists)

&allow-other-keys 常出现在宏 defun 中,表示调用这个函数会忽略无法识别的关键字参数而不会报错。

如下面实例中,调用函数 test 时的关键字参数:c 并不在其定义中,但由于指定了&allow-other-keys 所以并没有报错。

CL-USER> (defun test (a &key b &allow-other-keys) (+ a b))
TEST
CL-USER> (test 1 :b 3 :c 5)
4

3.2.1. lambda-list-keywords

lambda-list-keywords 是个常量,它保存着 A list of all the lambda list keywords.

在 SBCL 环境测试:

CL-USER> lambda-list-keywords
(&ALLOW-OTHER-KEYS &AUX &BODY &ENVIRONMENT &KEY SB-INT:&MORE &OPTIONAL &REST
 &WHOLE)

可看到 SBCL 中有个自己的扩展:SB-INT:&MORE

注:&allow-other-keys 等 ordinary lambda list 不仅仅可以用在 defun 中,它们可以用在下面这些 defined name 中:
define-method-combination, handler-case, restart-case, defun, labels, flet, lambda
参考:http://www.lispworks.com/documentation/HyperSpec/Body/03_da.htm

3.3. adjoin, pushnew

adjoin (函数), pushnew (宏)都可以可以往列表中增加元素。
但 adjoin 不会修改原来列表,pushnew 会直接修改原来列表:

CL-USER> (defparameter *test* ())
*TEST*
CL-USER> (adjoin 1 *test*)
(1)
CL-USER> *test*
NIL
CL-USER> (pushnew 1 *test*)
(1)
CL-USER> *test*
(1)

3.4. defvar, defparameter, defconstant

defvar, defparameter, defconstant 都是宏。

defvar 和 defparameter 用来定义全局变量,区别在于:defparameter 总是将初始值赋给变量,而 defvar 只有当变量未定义时才这样做。

defconstant 用来定义全局常量。

3.5. eq, eql, equal, equalp

eq, eql, equal, equalp 都是函数,用来做等价测试。
从 eq 到 equalq,等价的要求越来越宽松。

注:对数字,字符,字符串的等价测试一般用=,char=(或忽略大小写的 char-equal),string=(或忽略大小写的 string-equal)。

参考:
Practical Common Lisp, section 4.8
http://stackoverflow.com/questions/547436/whats-the-difference-between-eq-eql-equal-and-equalp-in-common-lisp

3.6. find, position

findposition 的功能类似,只是 find 返回元素,而 position 返回元素的下标(第 1 个元素下标为 0)。

find 和直觉不太一致!下面两次函数调用会分别返回哪个元素?

(find 7 '(3 7 9 10 11 5)  :test '<)
(find 7 '(3 7 9 10 11 5)  :test '< :from-end t)

结果是返回 9 和 11。
注:它并不是返回最左/最右比 7 小的元素。而是返回最左/最右的元素 item,那个元素满足:7 < item

实例:找出列表中第一个比指定元素大的数。

(find 7 '(3 7 9 10 11 5)  :test '<)  => 9

3.7. hash table 相关

参考:http://www.lispworks.com/documentation/HyperSpec/Body/18_aa.htm

Table 2: hashtable 相关函数
function/macro description
make-hash-table &key test size rehash-size rehash-threshold Creates and returns a new hash table
gethash 通过 key 找对应的 value
remhash Removes the entry for key in hash-table
clrhash Removes all entries from hash-table
sxhash Returns a hash code for object
hash-table-p 测试对象是不是 hash table
hash-table-count 返回 hash table 中条目的个数
hash-table-size 返回 hash table 的大小
hash-table-rehash-size 返回 rehash-size 值,它是 make-hash-table 时指定的一个参数
hash-table-rehash-threshold 返回 rehash-threshold 值,它是 make-hash-table 时指定的一个参数
hash-table-test 返回 hashtable 的 test 方法,它是 make-hash-table 时指定的一个参数
maphash Iterates over all entries in the hash-table
with-hash-table-iterator 也可用来遍历 hash table

在 SBCL 环境下测试 hash table 相关操作:

CL-USER> (defparameter *h* (make-hash-table))
*H*
CL-USER> (gethash 'foo *h*)
NIL
NIL
CL-USER> (setf (gethash 'foo *h*) 'value1)
VALUE1
CL-USER> (setf (gethash 'bar *h*) 'value2)
VALUE2
CL-USER> (gethash 'foo *h*)
VALUE1
T
CL-USER> (maphash #'(lambda (k v) (format t "~a => ~a~%" k v)) *h*)
FOO => VALUE1
BAR => VALUE2
NIL
CL-USER> (loop for value being the hash-values of *h*
            using (hash-key key)
            do (format t "~&~A => ~A" key value))
FOO => VALUE1
BAR => VALUE2
NIL

注 1:用 setf 和 gethash 可以往 hash table 中加入元素
注 2:上例中用 loop 实现了遍历 hash table

3.8. mapc, mapcar, mapcan, mapl, maplist, mapcon

Function MAPC, MAPCAR, MAPCAN, MAPL, MAPLIST, MAPCON

Syntax:
mapc function &rest lists+ => list-1
mapcar function &rest lists+ => result-list
mapcan function &rest lists+ => concatenated-results
mapl function &rest lists+ => list-1
maplist function &rest lists+ => result-list
mapcon function &rest lists+ => concatenated-results

函数式风格的一个重要方面是对高阶函数的使用,即那些接受其他函数作为参数或将函数作为返回值的函数。如 map 就是一个高阶函数,map 可以用于任何类型的序列,包括列表和向量。Common Lisp 还提供了 6 个特定于列表的映射函数。

mapcar 总是返回一个列表,它的第一个参数是想要应用的函数,而后续参数是其元素将为该函数提供实参的列表。函数被应用在列表实参的相继元素上,每次函数的应用会从每个列表中各消耗一个元素,每次函数调用的结果都被收集到一个新列表中。例如:

(mapcar #'(lambda (x) (* 2 x)) (list 1 2 3))  -> (2 4 6)
(mapcar #'+ (list 1 2 3) (list 10 20 30))  -> (11 22 33)

maplist 和 mapcar 较为相似,它们之间的区别在于 maplist 传递给函数的不是列表元素,而是实际的点对单元。这样,该函数不仅可以访问到列表中每个元素的值(通过点对单元的 CAR),还可以访问到列表的其余部分(通过 CDR)。

除构造结果的方式不同, mapcanmapcon 与 mapcar 和 maplist 的工作方式很相似,mapcar 和 maplist 会构造一个全新的列表来保存函数调用的结果,而 mapcan 和 mapcon 则通过将结果(必须是列表)用 nconc 拼接在一起来产生它们的结果。这样,每次函数调用都可以向结果中提供任意数量的元素。mapcan 像 mapcar 那样把列表的元素传递到映射函数中,而 mapcon 则像 maplist 那样来传递点对单元。

函数 mapcmapl 是伪装成函数的控制构造,它们只返回第一个列表实参,因此只有当映射函数的副作用有用时,它们才是有用的。mapc 是 mapcar 和 mapcan 的近亲,而 mapl 属于 maplist,mapcon 家族。

参考:《实用 Common Lisp 编程》12.6 节,映射

3.9. identity

identity is intended for use with functions that require a function as an argument.

identity could be defined by:

(defun identity (x) x)

实例:

(mapcan #'identity (list (list 1 2 3) '(4 5 6))) =>  (1 2 3 4 5 6)

Emacs Lisp 实例(Common Lisp 中没有函数 mapconcat):

(mapconcat 'identity '("" "home" "usr1") "/")  => "/home/usr1"

3.10. proclaim, declaim, declare

proclaim, declaim, declare 都可用来告诉编译器更多的信息,从而编译器可以更好地优化生成的代码。

Table 3: proclaim, declaim, declare 区别
name type 影响范围 说明
proclaim function global 它是函数,仅当调用时才执行。一般不使用它,而使用 declaim
declaim macro global 它是宏,在 compile-time 也有效。
declare symbol local 常用于声明函数参数类型等用途,让编译器知道更多信息。

从下面例子中可看出 proclaim 和 declaim 的区别:
假设有代码:

(proclaim '(special *x*))
(defun foo () (print *x*))
;; warning: undefined variable: *X*

编译器会输出一个 warning 信息:undefined variable: X

要去掉这个 warning,可以使用特殊操作符 eval-when 让函数 proclaim 在编译期间也执行:

(eval-when (:execute :compile-toplevel :load-toplevel)
   (proclaim '(special *x*)))
(defun foo () (print *x*))
;; no warning

还有一个更好的办法,就是使用宏 declaim:

(declaim (special *x*))
(defun foo () (print *x*))
;; no warning

参考:
http://stackoverflow.com/questions/14813801/proclaim-declaim-declare

3.11. prog1, prog2

prog1 first-form form* => result-1
prog2 first-form second-form form* => result-2

prog1prog2 是宏,会求值指定的各个 form,但它们的返回值不一样。
prog1 返回 first-form 的 primary value,而 prog2 返回 second-form 的 primary value。

CL-USER > (defparameter temp 1)
1
CL-USER > (prog1 (incf temp) (incf temp) (incf temp))
2
CL-USER > (defparameter temp 1)
1
CL-USER > (prog2 (incf temp) (incf temp) (incf temp))
3

3.12. set, setq, setf

set 是函数,setq 是特殊操作符,setf 是宏,都可用来改变变量的值。

(set (quote *foo*) 42)
(setq *foo* 42)
(setf (symbol-value '*foo*) 42)

参考;http://stackoverflow.com/questions/869529/difference-between-set-setq-and-setf-in-common-lisp

3.13. declare

为什么要用 declare?以 defun 中的 declare 为例说明:
声明的主要用法是为了告诉编译器关于变量和其他表达式的类型。例如,你可以通过编写下面的函数来告诉编译器,add 的两个参数都是 fixnum 的:

(defun add (x y)
  (declare (fixnum x y))
  (+ x y))

其中的 declare 表达式并非 Lisp 形式,相反它是 defun 语法的一部分,并且必须出现在函数体中其他任何代码之前。该声明声称为形参 x 和 y 传递的参数将总是 fixnum 的。换句话说,这是一个对编译器的承诺,并且编译器被允许生成假设你所言为真的代码。

参考:
实用 Common Lisp 编程
http://www.lispworks.com/documentation/lw51/CLHS/Body/d_type.htm

3.14. with-output-to-string

Macro WITH-OUTPUT-TO-STRING

Syntax:
with-output-to-string (var &optional string-form &key element-type) declaration* form*
=> result*

Arguments and Values:
var---a variable name.
string-form---a form or nil; if non-nil, evaluated to produce string.
string---a string that has a fill pointer.
element-type---a type specifier; evaluated. The default is character.
declaration---a declare expression; not evaluated.
forms---an implicit progn.
results--- If a string-form is not supplied or nil, a string; otherwise, the values returned by the forms.

Description:
with-output-to-string creates a character output stream, performs a series of operations that may send results to this stream, and then closes the stream.

实例 1:不指定 string-form,with-output-to-string 返回的是新建流中的字符串。

CL-USER > (with-output-to-string (s)
            (format s "here's some output")
            (format s " here's some other output"))
"here's some output here's some other output"

实例 2:指定 string-form,with-output-to-string 返回的是 form*的返回值。

CL-USER > (setq fstr (make-array '(0) :element-type 'base-char
                                 :fill-pointer 0 :adjustable t))
""
CL-USER > (with-output-to-string (s fstr)
            (format s "here's some output")
            (input-stream-p s))
NIL
CL-USER > fstr
"here's some output"

注:实例 2 中 with-output-to-string 返回的是最后一个 form,即(input-stream-p s)的返回值 NIL。fstr 在 with-output-to-string 中会被设置为"here's some output"

3.15. write, prin1, print, pprint, princ

Function WRITE, PRIN1, PRINT, PPRINT, PRINC

Syntax:
write object &key array base case circle escape gensym length level lines miser-width pprint-dispatch pretty radix readably right-margin stream
=> object
prin1 object &optional output-stream => object
princ object &optional output-stream => object
print object &optional output-stream => object
pprint object &optional output-stream => <no values>

Description:
write, prin1, princ, print, and pprint write the printed representation of object to output-stream.

write 是一个最 general 的函数,有很多参数可以输入,而 print1,princ,print,pprint 可以看做是 write 的封装。
如:

(prin1 object output-stream)
==  (write object :stream output-stream :escape t)

(princ object output-stream)
==  (write object stream output-stream :escape nil :readably nil)

(print object output-stream)
==  (progn (terpri output-stream)
           (write object :stream output-stream
                  :escape t)
           (write-char #\space output-stream))

(pprint object output-stream)
==  (write object :stream output-stream :escape t :pretty t)

princ 和 prin1 和 print 的区别:
The princ function sends a string after formatting any contol characters. E.g. (princ "\"this is quoted\"") will show as "this is quoted" on the command line.
The prin1 function does the same without formatting. E.g. (prin1 "\"this is quoted\"") will show as "\"this is quoted\"". This is usually used when sending to a file so you can later use the read function to get the same value. Also say you have a list of strings, it'll send the text to the file so a later read would produce the same list of strings - princ will ommit the quotes and the read would fail.
The print function does the same as the prin1, but prefixes the string with a newline "\n" (which is formatted). This so that you can send a control code string to a new line in a file in one instruction, otherwise you'd have to do a princ for the new line, then a prin1 for the data.

参考:http://forums.augi.com/showthread.php?111705-Difference-Between-PRINT-PRINC-amp-PRIN1

4. Functions

使用宏 defun 可以定义一个函数,定义函数的基本语法为:

(defun name (parameter*)
  "Optional documentation string."
  body-form*)

下面是定义函数的例子:

(defun verbose-sum (x y)
  "Sum any two numbers after printing a message."
  (format t "Summing ~d and ~d.~%" x y)
  (+ x y))

参考:http://www.gigamonkeys.com/book/functions.html

4.1. funcall VS. apply

funcallapply 的区别如下:

   (funcall function arg1 arg2 ...)
== (apply function (list arg1 arg2 ...))
== (apply function arg1 arg2 ... nil)

参考:https://stackoverflow.com/questions/3862394/when-do-you-use-apply-and-when-funcall

5. Conditional Constructs

Common Lisp 中条件分支语句如表 4 所示。

Table 4: Common Lisp Conditional Construct
Name 类型 语法形式
if Special Form if test then [else]
when Macro when test {form}*
unless Macro unless test {form}*
cond Macro cond {(test {form}*)}*
case Macro case keyform {({({key}*) | key} {form}*)}*
typecase Macro typecase keyform {(type {form}*)}*

参考:Common Lisp the Language, 2nd Edition, 7.6. Conditionals

6. Iteration Constructs

Common Lisp 中迭代语句如表 5 所示。

Table 5: Common Lisp Iteration Construct
Name 类型
loop Macro
do Macro
dolist Macro
dolist Macro
mapcar/maplist/mapc/mapl/mapcan/mapcon Function
prog, go Macro, Special Form

参考:Common Lisp the Language, 2nd Edition, 7.8. Iteration

7. Macros

宏是一段 Lisp 代码,使用宏可以产生另外一段 Lisp 代码。

比如 andor 都是宏(注:函数的参数是在求值之后才传递给函数的,所有,无法使用函数实现具有短路特性的 and 和 or)。

7.1. 自定义宏

使用 defmacro 可以定义自己的宏。

下面是使用宏实现 when 的例子:

(defmacro my-when (cond &rest body)
  (list 'if cond (cons 'progn body)))

可以使用反引号(可参考节 18.1 )来简化宏的定义,如上面的例子还可以写为:

(defmacro my-when (cond &rest body)
  `(if ,cond
       (progn ,@body)))

可以使用函数 macroexpand 来查看宏展开后的形式,如:

(macroexpand '(my-when (<1 2) (print "1<2"))) ; => (if (<1 2) (progn (print "1<2")))

8. Cons cells and Lists

Lisp 是“LISt Processing”的缩写,可见列表(List)在 Lisp 中的重要性。

8.1. 点对单元(Cons cells)

不介绍列表前,先介绍一种更简单的结构:点对单元(Cons cell)。

点对单元是“两个有顺序的值”,使用函数 cons (construct 简称)可以创建它们。 cons 接受两个参数并返回一个含有两个值的新点对单元。这些值可以是任何类型对象的引用。 除非第二个值是 nil 或者另外一个点对单元,否则点对都将打印成在括号中并用一个点分隔两个值的形式,即所谓的“点对”。

(cons 1 2) =>  (1 . 2)
(cons 1 nil) =>  (1)
(cons nil 2) =>  (NIL . 2)
(cons nil nil) =>  (NIL)
(cons 1 (cons 2 (cons 3 (cons 4 nil)))) =>  (1 2 3 4)
(cons 'a 'b) =>  (A . B)
(cons 'a (cons 'b (cons 'c '()))) =>  (A B C)
(cons 'a '(b c d)) =>  (A B C D)

点对单元中的两个值分别可由 car (Contents of Address part of Register)和 cdr (Contents of the Decrement part of Register)来获得。如:

(car (cons 1 2))  => 1
(cdr (cons 1 2))  => 2

8.2. Lists 本质

列表可为分两类:一是空列表 nil;二是“点对单元”的引用,往往由多个“点对单元”链状连接在一起。列表的元素被保存在点对单元的 car 中,而对后续“点对单元”的链接则被保存在 cdr 中,链上最后一个单元的 cdr 为 nil。

(cons 1 nil)                     => (1)
(cons 1 (cons 2 nil))            => (1 2)
(cons 1 (cons 2 (cons 3 nil)))   => (1 2 3)

函数 list 可以为你构建一些“点对单元”并将它们链接在一起。如上面的三个点对单元(列表)等价于:

(list 1)                         => (1)
(list 1 2)                       => (1 2)
(list 1 2 3)                     => (1 2 3)

用图例可以帮助我们更好地理解它们,“点对单元”可表示为:

+-----+-----+
| car | cdr |
+-----+-----+

列表 (1 2 3) 是由三个“点对单元”通过它们的 cdr 链接在一起而构成的,如下所示:

+-----+-----+     +-----+-----+     +-----+-----+
|  1  |  ---+---> |  2  |  ---+---> |  3  | nil |
+-----+-----+     +-----+-----+     +-----+-----+

列表 ("foo" (1 2) 10) 如下所示:

+-----+-----+     +-----+-----+     +-----+-----+
| \   |  ---+---> | \   |  ---+---> | 10  | nil |
+--\--+-----+     +--\--+-----+     +-----+-----+
    \                 \
     \                 \ +-----+-----+     +-----+-----+
      "foo"              |  1  |  ---+---> |  2  | nil |
                         +-----+-----+     +-----+-----+

8.3. Property List (plist)

属性列表(Property List)就是普通的列表。只是把列表元素当作两两组成的“indicator-value”对。

CL-USER> (list :a 1 :b 2 :c 3)            ; indicator常常为关键字符号,但这不是必须
(:A 1 :B 2 :C 3)
CL-USER> (getf (list :a 1 :b 2 :c 3) :a)  ; indicator :a对应的value为1
1
CL-USER> (getf (list :a 1 :b 2 :c 3) :c)  ; indicator :c对应的value为1
3

8.4. Association List (alist)

关联列表(Association List)就是由“点对单元”组成的列表。

CL-USER> (list '(a . 1) '(b . 2) '(c . 3))
((A . 1) (B . 2) (C . 3))

8.4.1. plist VS. alist

plist 上的操作一般是破坏性的(直接修改 plist 本身),而 alist 上的操作一般是非破坏性的(原 plist 不变,返回新的 plist)。

The difference is that a property list is an object with a unique identity; the operations for adding and removing property-list entries are destructive operations that alter the property list rather than making a new one. Association lists, on the other hand, are normally augmented non-destructively (without side effects) by adding new entries to the front (see acons and pairlis).

参考:https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node108.html

9. How to Find Your Way Around

9.1. apropos, apropos-list

Function APROPOS, APROPOS-LIST

Syntax:
apropos string &optional package => <no values>
apropos-list string &optional package => symbols

Arguments and Values:
string---a string designator.
package---a package designator or nil. The default is nil.
symbols---a list of symbols.

Description:
These functions search for interned symbols whose names contain the substring string.
For apropos, as each such symbol is found, its name is printed on standard output. In addition, if such a symbol is defined as a function or dynamic variable, information about those definitions might also be printed.
For apropos-list, no output occurs as the search proceeds; instead a list of the matching symbols is returned when the search is complete.

在 SBCL 环境下测试 apropos,得到类似下面的输出:

CL-USER> (apropos "map" 'common-lisp)
MAP (fbound)
MAP-INTO (fbound)
MAPC (fbound)
MAPCAN (fbound)
MAPCAR (fbound)
MAPCON (fbound)
MAPHASH (fbound)
MAPL (fbound)
MAPLIST (fbound)
; No value

9.2. describe, inspect

describe, inspect 可以用来查看对象的相关信息,inspect 是“交互版本”的 describe。它们的行为依赖于具体的实现。

在 SBCL 环境下运行 describe 和 inspect,会得到类似下面的输出:

CL-USER> (describe 'car)
COMMON-LISP:CAR
  [symbol]

CAR names a compiled function:
  Lambda-list: (LIST)
  Declared type: (FUNCTION (LIST) (VALUES T &OPTIONAL))
  Documentation:
    Return the 1st object in a list.
  Known attributes: foldable, flushable, unsafely-flushable
  Source file: SYS:SRC;CODE;LIST.LISP

(SETF CAR) names a compiled function:
  Lambda-list: (G32 G33)
  Derived type: (FUNCTION (T LIST) (VALUES T &OPTIONAL))
  Inline proclamation: INLINE (inline expansion available)
  Source file: SYS:SRC;CODE;SETF-FUNS.LISP

(SETF CAR) has setf-expansion: SB-KERNEL:%RPLACA
; No value
CL-USER> (inspect 'car)

The object is a SYMBOL.
0. Name: "CAR"
1. Package: #<PACKAGE "COMMON-LISP">
2. Value: "unbound"
3. Function: #<FUNCTION CAR>
4. Plist: NIL
> help

help for INSPECT:
  Q, E        -  Quit the inspector.
  <integer>   -  Inspect the numbered slot.
  R           -  Redisplay current inspected object.
  U           -  Move upward/backward to previous inspected object.
  ?, H, Help  -  Show this help.
  <other>     -  Evaluate the input as an expression.
Within the inspector, the special variable SB-EXT:*INSPECTED* is bound
to the current inspected object, so that it can be referred to in
evaluated expressions.
> Q

; No value

9.3. documentation

The generic function documentation returns the documentation string associated with the given object if it is available; otherwise it returns nil.

Syntax:
documentation x doc-type => documentation

Table 6: doc-type in documentation
doc-type related documentation
'variable defvar, defparameter, defconstant
'function defun, defmacro, special forms
'structure defstruct
'type deftype
'setf defsetf
'compiler-macro define-compiler-macro
'method-combination define-method-combination
't Documentation returned depends upon type of first argument.

在 SBCL 环境下测试 documentation,得到类似下面的输出:

CL-USER> (documentation 'car 'function)
"Return the 1st object in a list."
CL-USER> (documentation 'if 'function)
"IF predicate then [else]

If PREDICATE evaluates to true, evaluate THEN and return its values,
otherwise evaluate ELSE and return its values. ELSE defaults to NIL."

10. Format

Function FORMAT

Syntax:
format destination control-string &rest args => result

当 format 第一个参数(destination)是 NIL 时,format 会将输出生成到一个字符串中并返回它,其他情况下 format 均返回 NIL。

所有指令(control-string)都以波浪线开始并终止于单个字符(称为指令标识字符)。
某些指令带有前置参数(prefix parameter)来规定指令的行为,它们紧跟在波浪线后面,有多个时由逗号分隔。
某些指令使用修饰符(modifier,修饰符仅有 2 个:冒号和@)来调整指令的行为,修饰符放在前置参数之后,指令标识字符之前。

参考:
http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm
http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node200.html

10.1. 特殊前置参数 v 和#

v,使用一个格式化参数并将其值用作前置参数。
实例:使用前置参数 v

(format nil "~v%" 2)
(format nil "~2%")

上面两个输出相同,都将输出两个换行。

#,被求值为剩余的格式化参数的个数。
实例:使用前置参数#

(format nil "~#$" pi)
(format nil "~1$" pi)

上面两个输出相同,都将输出"3.1"

10.2. ~a, ~s 最通用的指令

最通用的指令是~a 和~s,它们使用一个任何类型的格式化参数,并将其输出。~a 输出成人类可读的形式,而~s 输出成可被 READ 读回来的形式。(a stands for Aesthetic, s stands for Standard)

在 SBCL 环境中测试如下:

CL-USER> (format nil "~a" 1)
"1"
CL-USER> (format nil "~s" 1)
"1"
CL-USER> (format nil "~a" "test")
"test"
CL-USER> (format nil "~s" "test")
"\"test\""

~a 和~s 接受 2 个修饰符和 4 个前置参数。
如冒号修饰符,可以将 nil 输出为()而不是 NIL。
在 SBCL 环境中测试如下:

CL-USER> (format nil "~a" nil)
"NIL"
CL-USER> (format nil "~:a" nil)
"()"

其它修饰符和前置参数的含义可参考:http://www.lispworks.com/documentation/HyperSpec/Body/22_cda.htm

10.3. ~%, ~& 输出换行

~%和~&用来输出换行。
两者区别在于~%总是产生一个新行,而~&只在当前没有位于一行开始处时才产生换行。

~%接受单个前置参数 n,前置参数指定换行的个数。
~n 接受单个前置参数 n,输出 n-1 或 n 个换行,取决与它是否在行首开始输出。

10.4. ~~ 输出波浪线

~~用来输出波浪线。

它接受单个前置参数,前置参数指定波浪线的个数。

(format nil "~10~") => "~~~~~~~~~~"

10.5. ~c 输出字符

~c 用来输出字符,不接受前置参数,可以使用冒号和@修饰符。

(format nil "~C" #\A) =>  "A"
(format nil "~C" #\Space) =>  " "

冒号修饰符:将空格、制表符和换行符这些不可打印的字符按它们的名字(函数 char-name 可以得到某个字符的名字)输出。
@修饰符:按 lisp reader 能理解的格式输出。
例子:~C 中的修饰符

(format nil "~:C" #\Space) =>  "Space"
(format nil "~@C" #\A) => "#\\A"

10.6. ~r, ~d, ~b, ~o, ~x 按进制输出整数

重点介绍~d 以十进制输出数字。~b ~o ~x 分别以二进制,八进制,十六进制形式输出数字,修饰符和前置参数和~d 类似。

冒号修饰符:在输出中添加逗号,每 3 个数位一个分组。
@修饰符:输出时总是打印数字的正负符号。

(format nil "~:d" 10000) => "10,000"
(format nil "~@d" 10000) => "+10000"

~d 共可接受 4 个前置参数:
第 1 个前置参数,指定输出的最小宽度。
第 2 个前置参数,指定用作占位符的字符。
第 3 个前置参数(需和冒号修饰符同时使用),指定用作数位组之间的分隔符的字符(默认为逗号)。
第 3 个前置参数(需和冒号修饰符同时使用),指定每组中数位的数量(默认为 3)。

(format nil "The answer is ~3,'0D." 5) =>  "The answer is 005."
(format nil "~,,'|,2:D" #xFFFF) =>   "6|55|35"

10.6.1. ~r 通用进制输出指令

~r 通用进制输出指令。它共接受 5 个前置参数,第 1 个前置参数用来指定所使用的进制,它是一个大于等于 2 小于等于 36 的数字。第 2 到第 5 个前置参数和~d 和 4 个前置参数含义一一对应。

注意:如果~r 不使用前置参数时,它的行为比较特殊,介绍如下(假设 arg 为 4):
~R prints arg as a cardinal English number: four.
~:R prints arg as an ordinal English number: fourth.
~@R prints arg as a Roman numeral: IV.
~:@R prints arg as an old Roman numeral: IIII.

CL-USER> (format nil "~r" 4)
"four"
CL-USER> (format nil "~:r" 4)
"fourth"
CL-USER> (format nil "~@r" 4)
"IV"
CL-USER> (format nil "~:@r" 4)
"IIII"

10.7. 控制流程: ~* 在格式化参数中跳转

~* 忽略下一个格式化参数。它接受单个前置参数,用于指定忽略的格式化参数的个数,即~n*表示忽略接下来的 n 个格式化参数。
结合前置参数和修饰符,可以实现在格式化参数间跳转。

10.7.1. 冒号修饰符

冒号修饰符允许最近使用的格式化参数再被使用一次。

(format nil "~d~:*~d" 1)  => "11"

前置参数和冒号修饰符同时使用,表示允许最近使用的 n 个格式化参数再被使用一次。

(format nil "~d~d~2:*~d~d" 1 2 3 4 5 6) => "1212"

10.7.2. @修饰符

@修饰符可跳转到格式化参数开始(~@*和~0@*含义相同)。

(format nil "~d~d~@*~d~d~d~d" 1 2 3 4 5 6) => "121234"

前置参数和@修饰符同时使用,表示跳转到第 n-1 个格式化参数(格式化参数的索引从 0 开始)。

(format nil "~d~d~0@*~d~d~d~d" 1 2 3 4 5 6) => "<121234>"
(format nil "~d~d~1@*~d~d~d~d" 1 2 3 4 5 6) => "<122345>"

10.8. 控制流程: ~? 在格式化参数中获取控制字符串

~? 可以从格式化参数中获取控制字符串。

一个~?消耗两个格式化参数,第 1 个格式化参数这控制字符串,第 2 个格式化参数必须为列表,对列表中的每个元素按控制字符串的要求依次输出。

(format nil "~?" "~A ~D ~D" '("Foo" 1 2 ))  => "Foo 1 2"

如果列表(第 2 个格式化参数)的元素个数多于控制字符串(第 1 个格式化参数)所要求的个数,则多于的元素将被忽略。

(format nil "~?" "~A ~D ~D" '("Foo" 1 2 3 4 5))  => "Foo 1 2"

10.8.1. @修饰符

一个~@?仅直接消耗一个格式化参数,这个格式化参数(已被用作控制字符)能消耗其它格式化参数。

(format nil "~@?" "<~A ~D ~D>" "Foo" 1 2) => "<Foo 1 2>"
(format nil "~@? ~D" "<~A ~D>" "Foo" 5 14) =>  "<Foo 5> 14"

10.9. 控制流程: ~[, ~] 条件格式化

参考:Practical Common Lisp, 18.7 Conditional Formatting

语法形式:以~[指令开始,~]指令结束,在它们之间包含由~;分隔的子句。
默认地,用格式化参数(必须为数字)来选取一个子句,由 format 处理。如:

(format nil "~[cero~;uno~;dos~;mucho~]" 0) => "cero"
(format nil "~[cero~;uno~;dos~;mucho~]" 1) => "uno"
(format nil "~[cero~;uno~;dos~;mucho~]" 2) => "dos"
(format nil "~[cero~;uno~;dos~;mucho~]" 3) => "mucho"

如果格式化参数的值大于等于子句的数量,则什么也不输出。
可以把最后一个子句指定为默认值(默认值只能是最后一个子句): 把最后子句的分隔符从~;改为~:;即可。

(format nil "~[cero~;uno~;dos~;mucho~]" 4) => ""
(format nil "~[cero~;uno~;dos~:;mucho~]" 100) => "mucho"

10.9.1. 前置参数

可以用前置参数来指定被选择的子句。如:

(format nil "~0[cero~;uno~;dos~;mucho~]") => "cero"
(format nil "~1[cero~;uno~;dos~;mucho~]") => "uno"
(format nil "~2[cero~;uno~;dos~;mucho~]") => "dos"
(format nil "~3[cero~;uno~;dos~;mucho~]") => "mucho"
(format nil "~4[cero~;uno~;dos~;mucho~]") => ""
(format nil "~100[cero~;uno~;dos~:;mucho~]") => "mucho"

10.9.2. 冒号修饰符

使用冒号修饰符时,~[和~]之间只能恰好包含两个子句。如果格式化参数为 nil 时使用第一个子句,其它情况下使用第二个子句。

(format nil "~:[fail~;pass~]" nil) => "fail"
(format nil "~:[fail~;pass~]" t) => "pass"

10.9.3. @修饰符

使用@修饰符,~[和~]之间只能包含一个子句。如果格式化参数不为 nil 时使用这个子句,如果为 nil 则跳过。如:

(format nil "~@[x=~A ~]~@[y=~A~]" 10 20) => "x=10 y=20"
(format nil "~@[x=~A ~]~@[y=~A~]" 10 nil) => "x=10 "
(format nil "~@[x=~A ~]~@[y=~A~]" nil 20) => "y=20"
(format nil "~@[x=~A ~]~@[y=~A~]" nil nil) => ""

10.10. 控制流程: ~{, ~} 迭代

参考:Practical Common Lisp, 18.8 Iteration

语法形式:以~{指令开始,~}指令结束。
默认地,它消耗一个格式化参数,这个参数必须是列表。

(format nil "~{~a, ~}" (list 1 2 3)) => "1, 2, 3, "

上面的输出中,结尾的逗号和空格让人讨厌,可以用~^来去掉它们。
~^在一个~{体内时,当列表中没有元素剩余时,~^会使迭代立即停止无需处理后面的控制字符。

(format nil "~{~a~^, ~}" (list 1 2 3)) => "1, 2, 3"

10.10.1. 冒号修饰符

使用冒号修饰符,格式化参数要求是列表的列表,在每个子列表上迭代地输出。如:

(format nil "~:{[~S=~S]~} end" '((a 1) (b 2) (c 3))) => "[A=1][B=2][C=3] end"

10.10.2. @修饰符

使用@修饰符,格式化参数不要求是列表了,~{把其余的格式化参数当作列表来处理。如:

(format nil "~@{~a, ~}" 1 2 3) => "1, 2, 3, "

11. Non-consing Function

Lisp 中很多函数对其参数是“安全”的,不会其修改参数。
还有很多函数以 n 开头,n的含义是 non-consing,即不分配新的点对单元。它们对其参数有副作用,会修改它!

实例:lst 在调用函数 nreverse 时被修改了

CL-USER> (defparameter lst '(a b c))
LST
CL-USER> (nreverse lst)
(C B A)
CL-USER> lst
(A)
Table 7: non-consing function 及对应“安全”的函数
对参数安全的 function non-consing function Comment
reverse nreverse  
append nconc  
butlast nbutlast (butlast '(a b c d)) => (A B C)
remove delete  
remove-if delete-if  
remove-if-not delete-if-not  
remove-duplicates delete-deplicates  
substitute nsubstitute 替换列表中的指定元素为另外的元素。如:(substitute 9 4 '(1 2 4 1 3 4 4)) => (1 2 9 1 3 9 9)
substitute-if nsubstitute-if  
substitute-if-not nsubstitute-if-not  
subst nsubst 在“树”上进行替换操作。如:(setq tree1 '(1 (1 2) (1 2 3) (1 2 3 4))) => (1 (1 2) (1 2 3) (1 2 3 4)), (subst "two" 2 tree1) => (1 (1 "tw"") (1 "two" 3) (1 "two" 3 4))
subst-if nsubst-if  
subst-if-not nsubst-if-not  
sublis nsublis  
revappend nreconc 参数为两个列表。反转第 1 个列表,再连接上第 2 个列表后整体返回。如:(revappend '(1 2 3) '(a b c)) => (3 2 1 A B C)
set-difference nset-difference 返回出现在第 1 个列表中,但没有出现在第 2 个列表中的元素组成的列表。如:(set-difference '(a b c d) '(b c)) => (D A)
set-exclusive-or nset-exclusive-or 返回在两个列表中仅出现一次的元素组成的列表。如:(set-exclusive-or '(a b c d) '(b c e f)) => (F E D A)
string-upcase nstring-upcase  
string-downcase nstring-downcase  
string-capitalize nstring-capitalize  

11.1. 小心“共享点对单元”

直接看实例,先定义 3 个列表:

(defparameter *list-1* (list 1 2))
(defparameter *list-2* (list 3 4))
(defparameter *list-all* (append *list-1* *list-2*))

测试 1:修改*list-1*

CL-USER> (setf (first *list-1*) 0)
0
CL-USER> *list-all*
(1 2 3 4)

结论 1:修改*list-1*并没有影响*list-all*

测试 2:修改*list-2*

CL-USER> (setf (first *list-2*) 0)
0
CL-USER> *list-all*
(1 2 0 4)

结论 2:修改*list-2*时影响了*list-all*

为什么会出现这样的结果?这得从 append 的实现说起。
考虑下面的例子:

(append (list 1 2) (list 3 4))  ->  (1 2 3 4)

从函数式观点来看,append 的工作是返回列表(1 2 3 4)而无需修改列表(1 2)和(3 4)中的任何点对单元。为了实现该目标,可以创建由四个新的点对单元组成的新列表,但这样做并无必要。实际上,append 只用两个新的点对单元来持有值 1 和 2,然后将它们连接在一起,并把第 2 个点对单元的 CDR 指向最一个实参,即列表(3 4)的头部。然后它返回含有 1 的那个新生成的点对单元。原先的点对单元都未被修改过,并且结果确实是列表(1 2 3 4)。唯一美中不足的是,append 返回的列表与列表(3 4)共享了一些点对单元。产生的结构如下所示:

cl_append.png

Figure 1: append 共享了点对单元

一般而言, append 必须复制除最后一个实参以外的所有其他实参,但它的返回结果却总是会与其最后一个实参共享结构。
类似地,其他一些函数也得用了列表共享结构的能力。一些像 append 这样的函数被指定总是返回以特定方式共享结构的结果。其他函数则被简单地允许根据具体实现来返回共享的结构。

参考:《实用 Common Lisp 编程》12.2 节,函数式编程和列表

12. Structures

defstruct defines a structured type, named structure-type, with named slots as specified by the slot-options.

(defstruct foo
   (slot名 默认值)
    ......
   (slot名 默认值))

同时还会自动生成 4 种函数:
make-foo(构造函数)
foo-slot 名(字段访问函数)
copy-foo(数据复制函数)
foo-p(数据类型判断函数)

实例:

CL-USER> (defstruct foo (a 10) (b nil) c)
FOO

CL-USER> (setq z1 (make-foo))
#S(FOO :A 10 :B NIL :C NIL)
CL-USER> (setq z2 (make-foo :b 20 :c 30))
#S(FOO :A 10 :B 20 :C 30)

CL-USER> (foo-a z1)
10
CL-USER> (setf (foo-a z1) 100)
100
CL-USER> z1
#S(FOO :A 100 :B NIL :C NIL)

CL-USER> (setq z3 (copy-foo z2))
#S(FOO :A 10 :B 20 :C 30)
CL-USER> (equalp z2 z3)
T

CL-USER> (foo-p z1)
T

说明:尽量不要用 defstruce(它出现在 CLOS 之前),而应当使用 defclass。

参考:Common Lisp the Language, 2nd Edition, 19 Structures: http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node168.html

13. Condition System

Lisp 的状况系统(Condition System)和 Java 或 C++中的异常处理有着相同的目标,但更加灵活。

参考:
http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node312.html
http://clhs.lisp.se/Body/09_a.htm

13.1. error, ignore-errors, handler-case

调用函数 error 可以表明程序遇到重大错误,无法继续进行。默认地,error 函数执行时会进入调试器。如:

 (defun factorial (x)
   (cond ((or (not (typep x 'integer)) (minusp x))
          (error "~S is not a valid argument to FACTORIAL." x))
         ((zerop x) 1)
         (t (* x (factorial (- x 1))))))
=>  FACTORIAL
(factorial 20)
=>  2432902008176640000
(factorial -1)
>>  Error: -1 is not a valid argument to FACTORIAL.
>>  To continue, type :CONTINUE followed by an option number:
>>   1: Return to Lisp Toplevel.
>>  Debug>

调用函数 ignore-errors 可以忽略错误,使 error 调用不会进入调试器。如:

(ignore-errors (factorial -1))
=>  NIL and #<SIMPLE-ERROR #x21004B216D>

handler-case 比函数 ignore-errors 更加通用,它可以为不同状况建立不同的处理器,还可以处理非错误状况。

handler-case 和 Java 中的 try/catch 语句比较类似。如,下面 Java 代码:

// Java code
try {
  doStuff();
  doMoreStuff();
} catch (SomeException1 se) {
  recover1(se);
} catch (SomeException2 se) {
  recover2(se);
}

对应的类似 Lisp 代码如下:

;; Lisp code
(handler-case
    (progn
      (do-stuff)
      (do-more-stuff))
  (some-exception1 (se) (recover1 se))
  (some-exception2 (se) (recover2 se)))

14. Package and Symbol

14.1. 什么是 Package

Common Lisp 中用包(package)来解决命名冲突问题。Common Lisp 中有 3 个标准包:COMMON-LISP (有别名 CL), COMMON-LISP-USER (有别名 CL-USER), KEYWORD。

14.2. list-all-packages, in-package

变量*PACKAGE*中保存着当前包的名字。
用函数 list-all-packages 可以查看当前环境下所有可用包,用宏 in-package 可以改变当前包。

在 SBCL 环境下运行 list-all-packages,会得到类似下面的输出:

CL-USER> (list-all-packages)
(#<PACKAGE "SWANK-IO-PACKAGE"> #<PACKAGE "SWANK"> #<PACKAGE "SWANK-RPC">
 #<PACKAGE "SWANK-MATCH"> #<PACKAGE "SB-CLTL2"> #<PACKAGE "SB-POSIX">
 #<PACKAGE "SB-INTROSPECT"> #<PACKAGE "SB-BSD-SOCKETS">
 #<PACKAGE "SB-BSD-SOCKETS-INTERNAL"> #<PACKAGE "SWANK-MOP">
 #<PACKAGE "SWANK-BACKEND"> #<PACKAGE "SWANK-LOADER"> #<PACKAGE "ASDF/FOOTER">
 #<PACKAGE "ASDF/USER"> #<PACKAGE "ASDF/INTERFACE">
 #<PACKAGE "ASDF/SOURCE-REGISTRY"> #<PACKAGE "ASDF/BACKWARD-INTERFACE">
 #<PACKAGE "ASDF/OUTPUT-TRANSLATIONS"> #<PACKAGE "ASDF/CONCATENATE-SOURCE">
 #<PACKAGE "ASDF/BUNDLE"> #<PACKAGE "ASDF/DEFSYSTEM">
 #<PACKAGE "ASDF/BACKWARD-INTERNALS"> #<PACKAGE "ASDF/OPERATE">
 #<PACKAGE "ASDF/PLAN"> #<PACKAGE "ASDF/LISP-ACTION"> #<PACKAGE "ASDF/ACTION">
 #<PACKAGE "ASDF/OPERATION"> #<PACKAGE "ASDF/FIND-COMPONENT">
 #<PACKAGE "ASDF/FIND-SYSTEM"> #<PACKAGE "ASDF/CACHE"> #<PACKAGE "ASDF/SYSTEM">
 #<PACKAGE "ASDF/COMPONENT"> #<PACKAGE "ASDF/UPGRADE"> #<PACKAGE "UIOP/DRIVER">
 #<PACKAGE "UIOP/BACKWARD-DRIVER"> #<PACKAGE "UIOP/CONFIGURATION">
 #<PACKAGE "UIOP/LISP-BUILD"> #<PACKAGE "UIOP/RUN-PROGRAM">
 #<PACKAGE "UIOP/IMAGE"> #<PACKAGE "UIOP/STREAM"> #<PACKAGE "UIOP/FILESYSTEM">
 #<PACKAGE "UIOP/PATHNAME"> #<PACKAGE "UIOP/OS"> #<PACKAGE "UIOP/UTILITY">
 #<PACKAGE "UIOP/COMMON-LISP"> #<PACKAGE "ASDF/PACKAGE">
 #<PACKAGE "UIOP/PACKAGE"> #<PACKAGE "COMMON-LISP-USER"> #<PACKAGE "SB-LOOP">
 #<PACKAGE "SB-EVAL"> #<PACKAGE "SB-WALKER"> #<PACKAGE "SB-UNIX">
 #<PACKAGE "SB-SEQUENCE"> #<PACKAGE "SB-PROFILE"> #<PACKAGE "SB-PRETTY">
 #<PACKAGE "SB-MOP"> #<PACKAGE "SB-THREAD"> #<PACKAGE "SB-GRAY">
 #<PACKAGE "SB-FORMAT"> #<PACKAGE "SB-FASL"> #<PACKAGE "SB-DISASSEM">
 #<PACKAGE "SB-DEBUG"> #<PACKAGE "SB-C"> #<PACKAGE "SB-BIGNUM">
 #<PACKAGE "SB-ASSEM"> #<PACKAGE "SB-ALIEN"> #<PACKAGE "SB-PCL">
 #<PACKAGE "SB-ALIEN-INTERNALS"> #<PACKAGE "SB-DI"> #<PACKAGE "KEYWORD">
 #<PACKAGE "SB-IMPL"> #<PACKAGE "SB-SYS"> #<PACKAGE "SB-KERNEL">
 #<PACKAGE "SB-VM"> #<PACKAGE "SB-INT"> #<PACKAGE "SB-EXT">
 #<PACKAGE "COMMON-LISP">)

14.3. use-package, unuse-package

use-package 使用某个包,如 use-package 'temp,把包 temp 中的 external symbols 设置为对当前包可见!而 unuse-package 则相反。

14.4. 什么是 Symbol

符号(Symbol)是有唯一名字的对象。 Lisp 中的符号(Symbol)和其它语言中的标识符(Identifier)类似,不过符号可以存储多个值。 可以把符号当作变量使用,当作函数使用,或者当作属性列表使用。

Common Lisp 中每个符号有表 8 所示的几个组成部分(Componentes,或称为 Cells)。

Table 8: Common Lisp 符号的组成部分
Cell Setter Getter Tester
符号的名字(name cell)   symbol-name  
符号的值(value cell) set, setq symbol-value boundp
函数(function cell)   symbol-function fboundp
属性列表(plist cell) setf (with get) symbol-plist, getf, get  
符号所属包(package cell)   symbol-package  

注 1:在 Common Lisp 中,同一个符号即可以是变量(保存在“value cell”中),也可以是函数(保存在“function cell”中),它们不会冲突,这称为 Lisp 2。而在 Scheme 中,一个符号不可能同时是变量和函数,这称为 Lisp 1。参考:https://en.wikipedia.org/wiki/Common_Lisp#The_function_namespace
注 2:不同的 Lisp 实现可能为 Symbol 增加其它 Cell。

14.5. 引用外部符号和内部符号

访问不在当前包中的外部符号时,可以在符号前指定包名和一个或两个冒号(:或::);要访问内部符号,在符号前指定包名和两个冒号(::)。

CL-USER> *package*
#<PACKAGE "COMMON-LISP-USER">
CL-USER> common-lisp:*package*
#<PACKAGE "COMMON-LISP-USER">
CL-USER> common-lisp::*package*
#<PACKAGE "COMMON-LISP-USER">

14.6. 关键字符号和 uninterned 符号

关键字符号在书写上以一个冒号“:”开始,这类符号在名为 KEYWORD 的包中创建并自动导出。
未进入任何包(uninterned)的符号在书写上以“#:”开始,当读取器读到一个带有“#:”的名字时会创建一个新的符号。

14.7. gensym, make-symbol

gensymmake-symbol 这两个函数的功能都是创造和返回一个符号。
两者的区别仅在于:新符号名字的选取方式不同。

14.8. find-symbol, intern

使用函数 find-symbol 或 intern 可以通过名字来查找包。
find-symbol 和 intern 的区别在于,当没有找到符号时,find-symbol 会直接返回 NIL,而 intern 会创建一个以该字符串命名的新符号并将其添加到包里。

(find-symbol "NEVER-BEFORE-USED") =>  NIL, NIL
(find-symbol "NEVER-BEFORE-USED") =>  NIL, NIL
(intern "NEVER-BEFORE-USED") =>  NEVER-BEFORE-USED, NIL
(intern "NEVER-BEFORE-USED") =>  NEVER-BEFORE-USED, :INTERNAL
(find-symbol "CAR" 'common-lisp) =>  CAR, :EXTERNAL
(intern "CAR" 'common-lisp) =>  CAR, :EXTERNAL

14.9. do-symbols, do-external-symbols, do-all-symbols

do-symbols, do-external-symbols, and do-all-symbols iterate over the symbols of packages.
其区别可参考:http://www.lispworks.com/documentation/HyperSpec/Body/m_do_sym.htm

14.9.1. 查找某个 package 中的外部符号

(do-external-symbols (s (find-package :cl)) (print s))

上面命令会输出 978 条记录(因为cl包中有978个外部symbol)!

15. Profiling

15.1. time, get-internal-run-time, get-internal-run-time

time, get-internal-run-time, get-internal-real-time 是 Common Lisp 中和 profiling 相关的宏或函数。

在 SBCL 环境测试:

CL-USER> (time (reduce #'+ (make-list 100000 :initial-element 1)))
Evaluation took:
0.003 seconds of real time
0.003735 seconds of total run time (0.003735 user, 0.000000 system)
133.33% CPU
8,673,262 processor cycles
1,605,632 bytes consed

100000
CL-USER> (defun timings (function)
           (let ((real-base (get-internal-real-time))
                 (run-base (get-internal-run-time)))
             (funcall function)
             (values (/ (- (get-internal-real-time) real-base) internal-time-units-per-second)
                     (/ (- (get-internal-run-time) run-base) internal-time-units-per-second))))
TIMINGS
CL-USER> (timings (lambda () (reduce #'+ (make-list 100000 :initial-element 1))))
7/1000
7/1000

参考:http://rosettacode.org/wiki/Time_a_function#Common_Lisp

注意:time 的输出不在*standard-output*中,而是在*trace-output*中。下面例子把 time 的输出重定向到文件/tmp/foo.txt 中:

CL-USER> (with-open-file (*trace-output* "/tmp/foo.txt"
                                         :direction :output
                                         :if-exists :append
                                         :if-does-not-exist :create)
           (time (format t "test time~&")))
test time
NIL

15.2. room

room prints, to standard output, information about the state of internal storage and its management.

在 SBCL 环境下运行 room,会得到类似下面的输出:

CL-USER> (room t)
Dynamic space usage is:   100,146,816 bytes.
Read-only space usage is:      5,856 bytes.
Static space usage is:         4,032 bytes.
Control stack usage is:        8,872 bytes.
Binding stack usage is:        1,056 bytes.
Control and binding stack usage is for the current thread only.
Garbage collection is currently enabled.

Breakdown for dynamic space:
  24,937,856 bytes for   348,072 instance objects.
  22,109,680 bytes for 1,381,855 cons objects.
  16,285,328 bytes for    15,945 code objects.
  11,293,040 bytes for    94,471 simple-vector objects.
   9,265,824 bytes for    33,228 simple-character-string objects.
   5,284,000 bytes for   160,355 closure objects.
  10,971,088 bytes for   246,513 other objects.
  100,146,816 bytes for 2,280,439 dynamic objects (space total.)
; No value

15.3. LispWorks 扩展

15.3.2. hcl:set-up-profiler, hcl:start-profiling, hcl:stop-profiling

LispWorks 的 hcl 包中提供了一些 profiler 相关函数,如:hcl:set-up-profiler, hcl:start-profiling, hcl:stop-profiling.
可以用宏 hcl:profile 来代替 hcl:start-profiling 和 hcl:stop-profiling.

LispWorks 实例:

(defun hello-world ()
  (format t "Hello World!~%"))

(defun main()
  (progn
    (hcl:set-up-profiler :package (list-all-packages))
    ;; (hcl:set-up-profiler :symbols '(hello-world main) :style :list)
    (hcl:start-profiling :processes :current)
    (dotimes (i 1000) (hello-world))
    (hcl:stop-profiling)))

(defun main2()
  (progn
    (hcl:set-up-profiler :package (list-all-packages))
    ;; (hcl:set-up-profiler :symbols '(hello-world main2) :style :list)
    (hcl:profile (dotimes (i 10000) (hello-world)))))

不要在解释执行的代码上运行 profiling,而应该在编译后的代码上运行 profiling,所以测试前先 deliver。
在 LispWorks 中 deliver 前面的函数后执行,得到类似下面的输出:

Call tree
Symbol                                                              seen   (%)
    1: SYSTEM::PRODUCT-START-FUNCTION                                 15 (100)
     2: MAIN                                                          15 (100)
      3: LW-XP:INTERNAL-FORMAT                                        15 (100)
       4: SYSTEM::SOFT-FORCE-OUTPUT                                   15 (100)
        5: (METHOD STREAM:STREAM-FORCE-OUTPUT
            (STREAM:BUFFERED-STREAM))                                 15 (100)
         6: CLOS::NEXT-METHOD-CALL-1                                  15 (100)
          7: (METHOD STREAM:STREAM-FLUSH-BUFFER
              (STREAM:BUFFERED-STREAM))                               15 (100)
           8: (METHOD STREAM:STREAM-WRITE-BUFFER
               (SYSTEM::TERMINAL-STREAM T T T))                       15 (100)
            9: SYSTEM::UNIX-WRITE                                     15 (100)


Cumulative profile summary
Symbol                                    called  profile   (%)      top   (%)
SYSTEM::SOFT-FORCE-OUTPUT                      0       15 (100)        0 (  0)
SYSTEM::PRODUCT-START-FUNCTION                 0       15 (100)        0 (  0)
LW-XP:INTERNAL-FORMAT                          0       15 (100)        0 (  0)
(METHOD STREAM:STREAM-FORCE-OUTPUT
 (STREAM:BUFFERED-STREAM))                     0       15 (100)        0 (  0)
(METHOD STREAM:STREAM-WRITE-BUFFER
 (SYSTEM::TERMINAL-STREAM T T T))              0       15 (100)        0 (  0)
SYSTEM::UNIX-WRITE                             0       15 (100)       15 (100)
MAIN                                           0       15 (100)        0 (  0)
CLOS::NEXT-METHOD-CALL-1                       0       15 (100)        0 (  0)
(METHOD STREAM:STREAM-FLUSH-BUFFER
 (STREAM:BUFFERED-STREAM))                     0       15 (100)        0 (  0)

参考:
http://www.lispworks.com/documentation/lw61/LW/html/lw-170.htm#pgfId-885977

16. Lexically, Dynamic Scope

Common Lisp 是一种词法作用域 (lexically scope) 的 Lisp。Scheme 是最早的有词法作用域的方言;在它之前,动态作用域 (dynamic scope) 被视为 Lisp 的本质属性之一。
词法作用域和动态作用域的区别在于语言处理自由变量的方式不同。当一个符号被用来表达变量时,我们称这个符号在表达式中是被绑定的 (bound),这里的变量可以是参数,也可以是来自像 let 和 do 这样的变量绑定操作符。如果符号不受到约束,就认为它是自由的。下面的例子具体说明了作用域:

(let ((y 7))
  (defun scope-test (x)
    (list x y)))

在函数表达式里,x 是受约束的,而 y 是自由的。自由变量有意思的地方就在于,这种变量应有的值并不那么显而易见。一个约束变量的值是确信无疑的当调用 scope-test 时,x 的值就是通过参数传给它的值。但 y 的值应该是什么呢?这要看具体方言的作用域规则。

下面语句会输出什么呢?

(let ((y 5))
  (scope-test 3))

先给出结论:在 Emacs Lisp 中输出(3 5),在 Common Lisp 中输出(3 7)。

在动态作用域的 Lisp 里,要想找出函数执行时自由变量的值,我们要往回逐个检查函数的调用链。 当发现 y 被绑定时,这个被绑定的值即被用在 scope-test 中。如果没有发现,那就取 y 的全局值。这样,在用动态作用域的 Lisp (如 Emacs Lisp) 里,上面的语句会输出(3 5)

在词法作用域的 Lisp 里,我们不再往回逐个检查函数的调用链,而是逐层检查定义这个函数时,它所处的各层外部环境。 所有在词法作用域的 Lisp (如 Common Lisp)里,上面的语句会输出(3 7)

尽管你仍然可以通过将变量声明为 special 来得到动态作用域,但是词法作用域是 Common Lisp 的默认行为。总的来说, Lisp 社区对昨日黄花的动态作用域几乎没什么留恋。因为它经常会导致痛苦而又难以捉摸的 bug。

参考:On Lisp, 2.5 Scope

16.1. 动态作用域例子

动态作用域会检查函数的调用链,看下例:

CL-USER > (defparameter *my-special-variable* 17)
*MY-SPECIAL-VARIABLE*
CL-USER > (defun show-my-special ()
            (declare (special *my-special-variable*))
            (print *my-special-variable*)
            nil)
SHOW-MY-SPECIAL
CL-USER > (defun do-something-else ()
            (show-my-special))
DO-SOMETHING-ELSE
CL-USER >  (defun dynamically-shadow-my-special ()
             (let ((*my-special-variable* 8))
               (do-something-else))
             (show-my-special))
DYNAMICALLY-SHADOW-MY-SPECIAL
CL-USER >  (dynamically-shadow-my-special)
8
17
NIL

从下面例子中可看到,使用动态作用域如果不小心设置了与函数定义中的自由变量相同的变量名,则会导致不期待结果的出现。

说明:show-my-special 函数中(declare (special my-special-variable))有没有都不会影响 dynamically-shadow-my-special 的输出结果,用 defparameter 声明的变量都是动态作用域 (special) 的,也就是说在此处没有必要显示地指定*my-special-variable*为 special。

参考:Successful Lisp http://psg.com/~dlamkins/sl/chapter08.html

16.2. 动态作用域 vs 词法作用域

Dynamic binding
All variable names and their values live in one global table.

Lexical binding
Every binding scope (function, let syntax, …) creates a new table of variable names and values, organised in a hierarchy called “the environment”.

Dynamic binding 的优点:
Dynamic bindings are great for modifying the behaviour of subsystems.

Lexical binding 的优点:
(1) much easier for the user [that is, programmer]
(2) much easier for the compiler to optimize

一般认为词法作用域更先进,它不仅仅是一种避免错误的手段,还带来了崭新的编程技术——闭包。

参考:
http://www.emacswiki.org/emacs/DynamicBindingVsLexicalBinding
http://www.gnu.org/software/emacs/emacs-paper.html#SEC17

16.3. Closure(闭包)

闭包是函数和变量绑定的组合。
闭包是带有局部状态的函数。

在 SBCL 环境中演示闭包:

CL-USER> (let ((counter 0))
           (defun new-id () (incf counter))
           (defun reset-id () (setq counter 0)))
RESET-ID
CL-USER> (new-id)
1
CL-USER> (new-id)
2
CL-USER> (new-id)
3
CL-USER> (reset-id)
0
CL-USER> (new-id)
1

从上面的例子中,有没有感觉到:“闭包是穷人的对象”。

Emacs Lisp 是动态作用域方言(注:从 Emacs 24 开始也支持词法作用域了),默认不支持闭包,执行上面函数 new-id 或 reset-id 会报如下错误:

Debugger entered--Lisp error: (void-variable counter)

参考:On Lisp, 2.6 Closures

16.3.1. 抽象代数中的闭包

“闭包(closure)”一词被用于定义两个毫不相关的概念,分别是数学领域抽象代数分支下用于描述集合之于运算的一种性质,以及计算机科学程序设计语言分支用于描述函数式语言所支持的一种机制。

抽象代数中的闭包是如下含义。如果将一运算应用于一集合中的元素,产生的仍然是该集合里的元素,则元素在该运算下封闭(闭包)。

参考:
闭包漫谈(从抽象代数及函数式编程角度)
http://kb.cnblogs.com/page/177108/

17. Type Hierarchy

Common Lisp 类型层级如图 2 和图 3 所示。

cl_type_hierarchy.gif

Figure 2: Common Lisp Type Hierarchy

来源:http://sellout.github.io/2012/03/03/common-lisp-type-hierarchy/

cl_types_in_clisp.png

Figure 3: Common Lisp Type Hierarchy (another example)

来源:http://www.informatimago.com/articles/cl-types/

18. Tips

18.1. 反引号表达式中的“,”和“,@”


反引号 ` 可以像单引号 ' 那样阻止表达式被求值,如;

CL-USER> `(1 2 3)
(1 2 3)
CL-USER> '(1 2 3)
(1 2 3)

不过和单引号不同的是, 在反引号 ` 表达式里,任何以 , 开始的子表达式都是被求值的。 如:

CL-USER> `(1 (+ 2 4) 3)
(1 (+ 2 4) 3)
CL-USER> `(1 ,(+ 2 4) 3)         ; 反引号会对逗号开始的子表达式进行求值
(1 6 3)

反引号中的 , 还有一个变体 ,@ ,它可以将接下来的表达式(必须求值成一个列表)的值嵌入到其外围的列表里。下面例子演示了 ,,@ 的不同:

CL-USER> `(and ,(list 1 2 3) 4)
(AND (1 2 3) 4)
CL-USER> `(and ,@(list 1 2 3) 4)
(AND 1 2 3 4)

参考:Common Lisp the Language, 2nd Edition, 22.1.3. Macro Characters

18.2. 用正则表达式查找符号

LispWorks 有个扩展(lw:regexp-find-symbols),支持用正则表达式查找符号。如:

;; To find all exported symbols that start with DEF:
(lw:regexp-find-symbols "^def" :external-only t)

18.3. 访问环境变量

没有统一的方法,不同 Common Lisp 的实现有不同的方法:

(defun my-getenv (name &optional default)
  #+CMU
  (let ((x (assoc name ext:*environment-list*
                  :test #'string=)))
    (if x (cdr x) default))
  #-CMU
  (or
   #+Allegro (sys:getenv name)
   #+CLISP (ext:getenv name)
   #+ECL (si:getenv name)
   #+SBCL (sb-unix::posix-getenv name)
   #+LISPWORKS (lispworks:environment-variable name)
   default))

参考:http://cl-cookbook.sourceforge.net/os.html

18.4. 访问命令行参数

没有统一的方法,不同 Common Lisp 的实现把命令行参数保存在不同的变量中:

(defun my-command-line ()
  (or
   #+SBCL *posix-argv*
   #+LISPWORKS system:*line-arguments-list*
   #+CMU extensions:*command-line-words*
   nil))

参考:http://cl-cookbook.sourceforge.net/os.html

18.5. deliver 程序

18.5.1. LispWorks

LispWorks Delivery User Guide: http://www.lispworks.com/documentation/lw61/DV/html/delivery.htm

第一步,准备应用程序,如 hello.lisp:

(in-package "CL-USER")
(defun hello-world ()
  (format t "Hello World!~%"))

第二步,准备 deliver 脚本,如 delivery.lisp:

(in-package "CL-USER")
(load-all-patches)
(defvar *delivered-image-name* "~/hello")
(compile-file (current-pathname "hello") :load t)
(deliver 'hello-world
         *delivered-image-name*
         0)

第三步,deliver 及测试程序

$ lispworks-6-1-0-x86-linux -build deliver.lisp
$ ~/hello
Hello World!

18.5.2. SBCL

SBCL 中函数 sb-ext:save-lisp-and-die 可以用来打包一个独立的可执行文件。
第一步,准备应用程序,如 hello-lisp.lisp:

(defun main ()
  (format t "hello lisp!~%"))

第二步,准备 deliver 脚本,如 deliver.lisp:

(progn
  (compile-file "~/hello-lisp.lisp")
  (load "~/hello-lisp.fasl")
  (save-lisp-and-die "hello-lisp" :toplevel #'main :executable t))

第三步,deliver 及测试程序

$ sbcl --script deliver.lisp
$ ./hello-lisp
hello lisp!

18.5.3. 其它工具

Buildapp is an application for SBCL or CCL that configures and saves an executable Common Lisp image.

其源码在:https://github.com/xach/buildapp

18.6. who-calls, who-binds, who-sets, who-references

who-calls 用于查找函数的 cross-referencing 信息;who-binds, who-references, who-sets 用于查找 special variable 的 cross-referencing 信息。但它们都不是标准中定义的函数,LispWorks 和 SBCL 等都支持它们。

Table 9: 查找 cross-referencing 信息
function LispWorks package SBCL package description
who-calls hcl sb-introspect Returns the callers of a function
who-binds hcl sb-introspect Returns the definitions which bind a special variable
who-references hcl sb-introspect Returns the definitions which reference a special variable
who-sets hcl sb-introspect Returns the definitions which set a special variable

在 SBCL 环境下测试 who-calls,会得到类似下面的输出:

CL-USER> (defun fun-a() (+ 1 1))
FUN-A
CL-USER> (defun fun-b() (fun-a))
FUN-B
CL-USER> (defun fun-c() (fun-a))
FUN-C
CL-USER> (sb-introspect:who-calls 'fun-a)
((FUN-C
  . #S(SB-INTROSPECT:DEFINITION-SOURCE
       :PATHNAME NIL
       :FORM-PATH (0 3 2)
       :CHARACTER-OFFSET 0
       :FILE-WRITE-DATE NIL
       :PLIST NIL
       :DESCRIPTION NIL))
 (FUN-B
  . #S(SB-INTROSPECT:DEFINITION-SOURCE
       :PATHNAME NIL
       :FORM-PATH (0 3 2)
       :CHARACTER-OFFSET 0
       :FILE-WRITE-DATE NIL
       :PLIST NIL
       :DESCRIPTION NIL)))

19. Others

19.1. Why doesn't Common Lisp have continuations?

Continuations are a great theoretical tool; if a language has first-class, multiply invocable continuations then one can build threads, exceptions, coroutines, and the kitchen sink on top.

However, there is an implementation burden with continuations; supporting first-class, multiply invocable continuations complicates things tremendously for the Lisp implementor. The ANSI standardizing committee J13, mindful of this, took the view that it would be better to specify the user-level control structure (CATCH, UNWIND-PROTECT, and so on) and let implementors choose whether to build those on top of continuations or not.

If you need to play with continuations, you should use a Scheme implementation.

Reference: https://common-lisp.net/project/lispfaq/FAQ/programming.html#id2520503

19.2. Common Lisp Pitfalls

Author: cig01

Created: <2012-10-20 Sat>

Last updated: <2018-06-05 Tue>

Creator: Emacs 27.1 (Org mode 9.4)