Lisp⼊门教程
Lisp ⼊门教程
作者:Geoffrey J. Gordon 1993/02/05 星期五
修订:Bruno Haible
翻译:刘鑫
整理:张泽鹏 2011/06/24 星期五
注意:这份 Common Lisp ⼊门教程是针对 CMU 环境编写,所以在其它环境运⾏ Lisp 时可能会有细节上的区别。
附:
据我所知最好的 Lisp 书籍是:Guy L. Steele Jr. 《Common LISP: the Language》 Digital Press. 1984.
第⼀版很容易阅读,第⼆版介绍了更新的标准。(两个标准的差异很⼩,对于粗⼼的程序员没有什么区别。)
我还记得 Dave Touretsky 写了⼀本,不过我从来没读过,所以不能对那本书发表评论。
Symbols
符号仅仅是字符串。你可以在符号中包含字母、数字、连接符等等,唯⼀的限制就是要以字母开头。(如果你只输⼊数字,最多再以⼀个连接符开头的话,LISP会认为你输⼊了⼀个整数⽽不是符号。)例如:
接下来我们可以做些事情。(">"标记表⽰你向LISP输⼊的东西,其它的是LISP 打印返回给你的。";"是LISP的注释符:";"后⾯的整⾏都会被忽略。)
有两个特殊的符号, t 和 nil 。 t 的值总是定义为 t,nil 的值总是定义为 nil 。LISP⽤ t 和 nil 代表 true 和false。以下是使⽤这个功能的 if 语句,后⾯再做详细说明:
最后⼀个例⼦看起来很怪,但是没有错:nil 代表 false ,其它任意值代表 true。(为了代码清晰,在没有什么特别原因的情况下,我们⽤t代表true。)
t 和 nil 这样的符号被称为⾃解析符号,因为他们解析为⾃⾝。⾃解析符号称为关键字;任⼀以冒号开头的符号都是关键字。
(下⾯是⼀些关键字的应⽤)如下所⽰:
Numbers
数值类型是数字⽂本,可能会以 + 或 - 开头。实数和整数很相像,但是它带有⼩数点,还可能写成科学计数法。有理数就像是两个整数之间带有⼀个/。LISP⽀持复数,写为#c(r i)(r表⽰实部,i表⽰虚部)。以上统称为数值。下⾯是⼀些数值:
标准的计算函数包括:+, -, *, /, floor, ceiling, mod, sin, cos, tan, sqrt, exp, expt 等等。所有这些函数都可以接受任意数值类型参数。+、-、* 和 / 返回尽可能⼤的类型:⼀个整数加⼀个有理数返回有理数,⼀个有理数加⼀个实数是⼀个实数,⼀个实数加⼀个复数是⼀个复数。如下所⽰:
对于整数来说,唯⼀的⼤⼩限制就是机器的内存。当然⼤数值运算(这会调⽤⼤整数)可能会很慢。(因此我们可以计算有理数,尤其是⼩整数和浮点数的⽐较运算)
Conses
cons 就是⼀个包含两个字段的记录。出于历史原因,两个字段分别被称为 "car"和"cdr"。(在第⼀台实现LISP的机器上,⽤CAR和CDR代表"地址寄存器的内容"和"指令寄存器的内容"。Conses的实现主要依靠这两个寄存器。)
Conses很容易使⽤:
Lists
你可以构造conses之外的结构。可能最简单的是链表:每⼀个cons的car指向链表的⼀个元素,cdr指向
另⼀个cons或者nil。我们可以使⽤list函数构造链表。
需要注意的是 LISP ⽤⼀种特殊的⽅式打印链表:它忽略掉某些分隔和括号。规则如下:如果某个 cons 的 cdr 是 nil ,LISP 不打印 nil 和段标记,如果 cons A 的 cdr 是 cons B,LISP不打印 cons B 的括号和 cons A 的分隔符。如下:
最后⼀个例⼦相当于调⽤(list 4 5 6)。要注意的是这⾥ nil 表⽰没有元素的空链表:包含两个元素的链表(a b)中,cdr是(b),⼀个含有单个元素的链表;包含⼀个元素的链表(b),cdr是nil,故此这⾥必然是⼀个没有元素的链表。
如果你把链表存储在变量中,可以将它当作堆栈来使⽤:
Functions
前⾯我们讨论过⼀些函数的例⼦,这⾥还有更多:
当我们定义函数的时候,设定了两个参数,x 和 y。现在当我们调⽤ foo,需要给出两个参数:第⼀个在foo 函数调⽤时成为 x 的值,第⼆个成为 y 的值。在LISP中,⼤部分的变量都是局部的,如果 foo 调⽤了bar,bar中虽然使⽤了名字为 x 的引⽤,但bar 得不到 foo 中的 x。
在调⽤过程中给⼀个符号赋值的操作被称为绑定。
我们可以给函数指定可选参数,在符号 &optional 之后的参数是可选参数:
bar 函数的调⽤规则是要给出⼀个或两个参数。如果它⽤⼀个参数调⽤,x 将会绑定到这个参数值上,⽽y 就是 nil;如果⽤两个参数调⽤它,x和y会分别绑定到第⼀和第⼆个值上。
baaz 函数有两个可选参数。它为它们分别提供了默认值:如果调⽤者只给出了⼀个参数,z会绑定为10⽽不是nil,如果调⽤者没有给出参数,x会绑定为3,⽽z绑定为10。
在参数列表的最后设置⼀个 &rest 参数,可以使我们的函数接受任意数⽬的参数。LISP把所有的附加参数都放进⼀个链表并绑定到 &rest 参数。如下:
最后,我们可以为函数指定⼀种被称为关键字参数的可选参数。调⽤者可以⽤任意顺序调⽤这些参数,因为他们已经通过关键字标⽰出来。
关键字参数也可以有默认值:
Printing
某些函数可以⽤来输出。最简单的⼀个是 print,它可以打印参数并且返回它们。
⾸先打印3,然后返回它。
如果你需要更复杂的输出,可能会⽤到 format,这⾥有个例⼦:
第⼀个参数可以是 t,nil 或者⼀个流。t意味着输出到终端;nil意味着不打印任何东西,⽽是把它返回。流是⽤于输出的通⽤⽅式:它可以是⼀个指定的⽂件,或者⼀个终端,或者另⼀个程序。这⾥不再详细描述流
的更多细节。
第⼆个参数是个格式化模版,即⼀个包含格式化设定的字符串。
所有其它的参数由格式化设定引⽤。LISP会根据标⽰所引⽤的参数,将其替换为合适的字符,并返回结果
字符串。
如果format的第⼀个参数是 nil,它返回⼀个字符串,什么也不打印,否则它总是返回 nil。
前⾯的例⼦中有三种不同的标⽰:~S,~D和~%。第⼀个接受任意LISP对象并且将其替换为这个对象的打印
描述(与使⽤print打印出的描述信息相同)。第⼆个接受⼀个整数。第三个总是替换为⼀个回车符。
另⼀个常⽤的标⽰是~~,它替换为单个~。
LISP⼿册中介绍了其它(很多,很多)的格式化标⽰。
我们输⼊到LISP解释器的东西被称为语句;LISP解释器逐条循环读取每条语句,进⾏解析,将结果打印出来。这个过程被称为读取-解析-打印循环。
某些语句会发⽣错误,LISP会引领我们进⼊调试器,以便我们出错误原因。LISP的各种调试器有很多差异,不过使⽤"help"或":help"命令就会给出⼀些语句帮助。
通常,⼀个语句是⼀个原⼦(例如,⼀个符号或者整数,或者字符串)或者⼀个列表,如果换某个语句是
原⼦,LISP⽴即解析它。符号解析为它们的值;整数和字符串解析为它们⾃⾝。如果语句是⼀个列表,LISP视
它的第⼀个元素为函数名;它递归的解析其余的元素,然后将它们的值作为参数来调⽤这个函数。
字符串按照反斜杠转成数组例如,如果LISP遇到语句 (+ 3 4),它尝试将 + 作为函数名。然后将 3 解析为 3,4 解析为4;最后⽤3 和 4 作为参数调⽤ +。LISP打印出 + 函数的返回值7。
顶级循环还提供了⼀些其它的便利;⼀个特别⽅便的地⽅就是获取以前输⼊的语句的结果。LISP总会保存最近三个结果;它将它们保存在 *,** 和 *** 三个符号的值中,例如:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论