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

tnil是两个特殊符号,定义为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),cdrnil,故此这里必然是一个没有元素的链表。

nilcarcdr定义为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 中编写程序不同,letlet*语句提供了词汇作用域。动态作用域是我们在 BASIC 里用的那种:如果我们给一个动态作用域变量赋了值,那么所有对这个变量的访问都会取得这个值,直到给同一个变量赋了另一个值为止。

在 LISP 中,动态作用域变量被称为特化变量 。你可以用special 语句 defvar定义一个特化变量。这里有一些词汇化和动态作用域变量的示例。

在以下示例中,check-regular 函数引用一个 regular(也就是一个词汇作用域)变量。因为 check-regular 在绑定 regularlet外部词汇化,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。
blog comments powered by Disqus

Valid XHTML 1.0 Strict This page is Vim powered