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 |
                         +-----+-----+     +-----+-----+

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

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

14.1 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.2 use-package, unuse-package

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

14.3 引用外部符号和内部符号

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

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.4 关键字符号和uninterned符号

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

14.5 gensym, make-symbol

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

14.6 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.7 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.7.1 查找某个package中的外部符号

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

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

14.8 defpackage

[TODO]

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是动态作用域方言,默认不支持闭包,执行上面函数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.png

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 8: 查找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 00:00>

Last updated: <2018-02-06 Tue 16:46>

Creator: Emacs 25.3.1 (Org mode 9.1.4)