Lisp学习笔记
本文是Common Lisp Hint阅读笔记,thanks to:
Geoffrey J. Gordon :ggordon@cs.cmu.edu
Bruno Haible :haible@ma2s2.mathematik.uni-karlsruhe.de
刘鑫 :March.Liu@gmail.com
Symbols
包含字母、数字连字符等的字符串,要求以字母开头。如:
a b cl foo bar baaz-quux-garply
接下来我们可以做些事情。 (">"标记表示你向 LISP 输入的东西, 其它的是 LISP打印返回给你的。";"是 LISP 的注释符:";"后面的整行都会被忽略。)
> (setq a 5) ;store a number as the value of a symbol 5 > a ;take the value of a symbol 5 > (let ((a 6)) a) ;bind the value of a symbol temporarily to 6 6 > a ;the value returns to 5 once the let is finished 5 > (+ a 6) ;use the value of a symbol as an argument to a function 11
t
和nil
是两个特殊符号,定义为true和false。如:
> (if t 5 6) 5 > (if nil 5 6) 6 > (if 4 5 6) 5
最后一个很怪,因为nil
代表false
,其他任意值代表true
,为代码清晰尽量使用t
代表true
。
t 和 nil这样的符号被称为自解析符号
,因为他们解析为自身。自解析符号称为关键字
;任一以冒号开头
的符号都是关键字。(下面是一些关键字的应用)如下所示:
> :this-is-a-keyword :THIS-IS-A-KEYWORD > :so-is-this :SO-IS-THIS > :me-too :ME-TOO
Numbers
数值是可能以+
或-
开头的数字,可以带有小数点,写成分号形式,并支持复数形如#c(r i)
.其中r
表示实数,i
表示虚数。下面是一些数值:
5 17/5 -34 +6 3.1415 1.722e-15 #c(1.722e-150.75)
- 标准的计算函数包括
+ - * / floor ceiling mod sin cos tan sqrt exp expt...
所有这些函数都可以接受任意数值类型参数。+、-、* 和 /返回尽可能大的类型:一个整数加一个有理数返回有理数,一个有理数加一个 实数 是一个实数,一个实数加一个复数是一个复数。如下所示:
> (+ 3 3/4) ;type contagion 15/4 > (exp 1) ;e 2.7182817 > (exp 3) ;e*e*e 20.085537 > (expt 3 4.2) ;exponent with a base other than e 100.90418 > (+ 5 6 7 (* 8 9 10)) ;the fns +-*/ all accept multiple arguments 738
cons
就是一个包含两个字段的记录。出于历史原因,两个字段分别被称为"car"和"cdr"。(在第一台实现 LISP 的机器上,用 CAR 和 CDR 代表"地址寄存器的内容"和"指令寄存器的内容"。Conses 的实现主要依靠这两个寄存器。)
Conses 很容易使用:
> (cons 4 5) ;Allocate a cons. Set the car to 4 and the cdr to 5. (4 . 5) > (cons (cons 4 5) 6) ((4 . 5) . 6) > (car (cons 4 5)) 4 > (cdr (cons 4 5)) 5
Lists
你可以构造 conses 之外的结构。可能最简单的是链表:每一个 cons 的 car 指向链表的一个元素,cdr 指向另一个 cons 或者 nil。我们可以使用 list 函数构造链表。
> (list 4 5 6) (4 5 6)
需要注意的是 LISP用一种特殊的方式打印链表:它忽略掉某些分隔和括号。规则如下:
如果某个 cons 的 cdr 是 nil ,LISP 不打印 nil和段标记,如果cons A 的 cdr 是 cons B,LISP 不打印 cons B 的括号和 cons A的分隔符。
如下:
> (cons 4 nil) (4) > (cons 4 (cons 5 6)) (4 5 . 6) > (cons 4 (cons 5 (cons 6 nil))) (4 5 6)
最后一个例子相当于调用(list 4 5 6)
。要注意的是这里
nil
表示没有元素的空链表:包含两个元素的链表`(a
b)中,
cdr 是
(b)`,一个含有单个元素的链表;包含
一个元素的链表(b)
,cdr
是 nil
,故此这里必然是一个没有元素的链表。
nil
的car
和cdr
定义为nil
。
如果你把链表存储在变量中,可以将它当作堆栈来使用:
> (setq a nil) NIL > (push 4 a) (4) > (push 5 a) (5 4) > (pop a) 5 > a (4) > (pop a) 4 > (pop a) NIL > a NIL
Function
一些函数的例子:
> (+ 3 4 5 6) ;this function takes any number of arguments 18 > (+ (+ 3 4) (+ (+ 4 5) 6)) ;isn't prefix notation fun? 22 > (defun foo (x y) (+ x y 5)) ;defining a function FOO > (foo 5 0) ;calling a function 10 > (defun fact (x) ;a recursive function (if (> x 0) (* x (fact (- x 1))) 1)) FACT > (fact 5) 120 > (defun a (x) (if (= x 0) t (b (- x)))) ;mutually recursive functions A > (defun b (x) (if (> x 0) (a (- x 1)) (a (+ x 1)))) B > (a 5) T > (defun bar (x) ;a function with multiple statements in (setq x (* x 3)) ;its body -- it will return the value (setq x (/ x 2)) ;returned by its final statement (+ x 4)) BAR > (bar 6) 13
函数中的大部分变量是局部的
。
在调用过程中给一个符号赋值的操作称为绑定
。
我们可以给函数指定可选参数,在符号&optional
之后的参数是可选参数:
> (defun bar (x &optional y) (if y x 0)) BAR > (defun baaz (&optional (x 3) (z 10)) (+ x z)) BAAZ > (bar 5) 0 > (bar 5 t) 5 > (baaz 5) 15 > (baaz 5 6) 11 > (baaz) 13
bar
函数的调用规则是要给出一个或两个参数。如果它用一个参数调用,x将会绑定到这个参数值上,而 y 就是nil;如果用两个参数调用它,x 和 y 会分别绑定到第一和第二个值上。
bazz
函数有两个可选参数。它为它们分别提供了默认值:如果调用者只给出了一个参数,z 会绑定为 10 而不是 nil,如果调用者没有给出参数,x 会绑定为 3,而z绑定为 10。
在参数列表的最后设置一个 &rest
参数,可以使我们的函数接受任意数目的参数。LISP 把所有的附加参数都放进一个链表并绑定到 &rest
参数。如下:
> (defun foo (x &rest y) y) FOO > (foo 3) NIL > (foo 4 5 6) (5 6)
最后,我们可以为函数指定一种被称为关键字参数的可选参数。调用者可以用任意顺序调用这些参数,因为他们已经通过关键字标示出来。
> (defun foo (&key x y) (cons x y)) FOO > (foo :x 5 :y 3) (5 . 3) > (foo :y 3 :x 5) (5 . 3) > (foo :y 3) (NIL . 3) > (foo) (NIL)
关键字参数也可以有默认值:
> (defun foo (&key (x 5)) x) FOO > (foo :x 7) 7 > (foo) 5
Printing
某些函数可以用来输出。最简单的一个是print,它可以打印参数并且返回它们。
> (print 3) 3 3
首先打印 3,然后返回它。
如果你需要更复杂的输出,可能会用到format,这里有个例子:
> (format t "An atom: ~S~%and a list: ~S~%and an integer: ~D~%" nil (list 5) 6) An atom: NIL and a list: (5) and an integer: 6
第一个参数可以是 t,nil或者一个流。t 意味着输出到终端;nil 意味着不打印任何东西, 而是把它返回。 流是用于输出的通用方式: 它可以是一个指定的文件,或者一个终端,或者另一个程序。这里不再详细描述流的更多细节。
第二个参数是个格式化模版,即一个包含格式化设定的字符串。
所有其它的参数由格式化设定引用。LISP 会根据标示所引用的参数,将其替换为合适的字符,并返回结果字符串。
如果 format 的第一个参数是 nil
,它返回一个字符串,什么也不打印,否则它总是返回 nil
。
前面的例子中有三种不同的标示:~S
,~D
和~%
。第一个接受任意 LISP 对象并且
将
其替换为这个对象的打印描述(与使用 print 打印出的描述信息相同)。
另一个常用的标示是~~
,它替换为单个~
。
LISP 手册中介绍了其它(很多,很多)的格式化标示。
Forms and the Top-Level Loop
我们输入到 LISP 解释器的东西被称为语句; LISP 解释器逐条循环读取每条语句,进行解析,将结果打印出来。这个过程被称为读取-解析-打印循环。
某些语句会发生错误,LISP 会引领我们进入调试器,以便我们找出错误原因。LISP 的各种调试器有很多差异,不过使用"help"或":help"命令就会给出一些语句帮助。
通常,一个语句是一个原子(例如,一个符号或者整数,或者字符串)或者一个列表,如果换某个语句是原子,LISP 立即解析它。符号解析为它们的值;整数和字符串解析为它们自身。如果语句是一个列表,LISP 视它的第一个元素为函数名;它递归的解析 其余的元素,然后将它们的值作为参数来调用这个函数。
例如,如果 LISP 遇到语句 (+ 3 4)
,它尝试将 +
作为函数名。然后将 3 解析为 3,4 解析为 4;最后用 3 和 4 作为参数调用+。LISP 打印出 +
函数的返回值 7。
顶级循环还提供了一些其它的便利;一个特别方便的地方就是获取以前输入的语句的结果。LISP 总会保存最近三个结果;它将它们保存在*
,**
和***
三个符号的值中,例如:
> 3 3 > 4 4 > 5 5 > *** 3 > *** 4 > *** 5 > ** 4 > * 4
Special forms
有几个特殊语句看起来像函数调用,但其实不是。这里面包括流程控制语句,例如 if 语句和 do loops;赋值语句,例如 setq, setf,push 和 pop;定义语句例如 defun 和 defstruct;以及绑定构造,如let。(这里没有提及所有的特殊语句。我们继续。)
一个很有用的特殊语句是 quote:
quote 取消其参数的绑定状态。例如:
> (setq a 3) 3 > a 3 > (quote a) A > 'a ;'a is an abbreviation for (quote a) A
另一个类似的语句是fuction:
function 使得解释器将其参数视为一个函数而不是解析值,例如:
> (setq + 3) 3 > + 3 > '+ + > (function +) #<Function + @ #x-fbef9de> > #'+ ;#'+ is an abbreviation for (function +) #<Function + @ #x-fbef9de>
当我们需要将一个函数作为参数传递给另一个函数时会用到 function 语句。后面有些示例函数将函数作为参数。
Binding
绑定是词汇作用域赋值(汗,怎么读怎么别扭--译者)。每当函数调用时,它就发生于函数的参数列变量中:形式参数被取代为调用函数时的实际参数。你可以在程序中随处绑定变量,就像下面这样:
(let ((var1 val1) (var2 val2) ...) body)
let 把 val1 绑定到 var1,把 val2 绑定到 var2,依次类推;然后在它的程序体中执行语句。let 的程序体与函数体的执行规则完全相同。例如:
> (let ((a 3)) (+ a 1)) 4 > (let ((a 2) (b 3) (c 0)) (setq c (+ a b)) c) 5 > (setq c 4) 4 > (let ((c 5)) c) 5 > c 4
你可以用 (let (a b) ...)
代替 (let ((a nil) (b nil)) ...)
。
val1,val2 等等。在 let 内部不能引用var1,var2 等等 let 正在绑定的成员。例如(简而言之,在参数表中,形式参数之间不能互相引用--译者):
> (let ((x 1) (y (+ x 1))) y) Error: Attempt to take the value of the unbound symbol X
如果符号 x 已经有了一个全局值,会产生一些奇怪的结果:
> (setq x 7) 7 > (let ((x 1) (y (+ x 1))) y) 8
let*
语句类似于let
,但它允许引用之前在let*
中定义的变量的值。例如:
> (setq x 7) 7 > (let* ((x 1) (y (+ x 1))) y) 2
语句
(let* ((x a) (y b)) ...)
等价于
(let ((x a)) (let ((y b)) ...))
Dynamic Scoping
与我们在 C 或 Pascal 中编写程序不同,let
和 let*
语句提供了词汇作用域。动态作用域是我们在 BASIC 里用的那种:如果我们给一个动态作用域变量赋了值,那么所有对这个变量的访问都会取得这个值,直到给同一个变量赋了另一个值为止。
在 LISP 中,动态作用域变量被称为特化变量 。你可以用special
语句 defvar
定义一个特化变量。这里有一些词汇化和动态作用域变量的示例。
在以下示例中,check-regular 函数引用一个 regular(也就是一个词汇作用域)变量。因为 check-regular 在绑定 regular 的 let
外部词汇化,check-regular 返回变量的全局值。
> (setq regular 5) 5 > (defun check-regular () regular) CHECK-REGULAR > (check-regular) 5 > (let ((regular 6)) (check-regular)) 5
在这个例子中,函数 check-special 引用了一个特化(动态作用域)变量。因此 check-special 调用临时发生于特化绑定的 let内部, check-special 返回变量的局部值。
> (defvar *special* 5) *SPECIAL* > (defun check-special () *special*) CHECK-SPECIAL > (check-special) 5 > (let ((*special* 6)) (check-special)) 6
方便起见,特化变量以一个 *
开始和结束。特化变量主要用于全局变量,因为程序员通常期望局部变量使用词汇作用域,全局变量使用动态作用域。
词汇和动态作用域的更多差异参见《Common LISP: the Language》。
2012年02月15日 星期三 15时43分13秒
待续,在ubuntu中文上看到了,可能不再更新。开始看pcl。