0. Lisp系统的交互式前端(REPL)
刚接触Lisp,会发现每种Lisp的实现都会带有REPL(read - eval - print -loop)。
这里记录一下REPL相关的说明及问题。
顶层(toplevel)
在任何lisp系统中的交互式前端(repl),叫做顶层
。中断循环(break loop)
如果输入了一些Lisp无法理解的东西,它会打印出一个错误信息,然后进入一个叫做
中断循环的顶层。
中断循环给予有经验的程序员一个找出错误原因的大集会,不过对于初学者可能只能
跳出这个循环了。
每个Lisp实现的REPL的中断循环跳出的方式不通,请根据提示使用。
1. 形式(Form)
Lisp中使用S表达式进行程序的书写。通过左括号( 表示表达式开始,右括号 )表示表达式结束。
正式因为使用了这种表达式,所以Lisp的语法十分简单,因为除了像1
这种原子外,都是括号中的表达式。
2. 求值(Evaluation)
Lisp使用前缀表达式,类似(+ 1 2)这种形式。括号内的第一个元素是函数,后面的就是函数的参数。
Lisp中使用quote作为一种保护表达式不被求值
的方式,quote也可以缩写为'
。
3. 数据(Data)
Lisp提供了在其他预言中找的到的及找不到的数据类型。如,整数(integer)、字符串(string)等其他语言中可以找到的数据类型。还有如,符号(symbol)与列表(lists)等,在其他语言中找不到的数据类型。
符号
(通常) 不对自己身求值,所以需要使用quote引用它。列表
是由被括号包裹住的零个或多个元素来表示的。括号内的元素可以是任何类型。
使用列表不许要引用(quote或者其缩写'),不然Lisp会以为是一个函数调用。list
函数可以用来创建列表。但不同的是,由于list是函数,所以它的实参自然会被求值。
4. 列表操作(List Operations)
cons
函数用来构造列表car
函数获取列表的第一个元素cdr
函数获取列表除第一个元素外的所有元素third
函数可以获取列表的第三个函数,通过的还有first、second等等。
5. 真与假(Truth)
在Common Lisp中,t
表示逻辑真
,nil
表示逻辑假
。
将返回值逻辑真
或逻辑假
的函数称为谓词(predicate)
。所以谓词的名字通常以p
结尾。
-
listp
函数判断是否为列表。 -
null
函数判断是否为空(假)。 -
not
函数对参数进行逻辑非
判断。 -
if
条件,通常接受三个表达式,一个test表达式,一个then表达式和一个else表达式。若test为逻辑真
则对then表达式求值,并返回表达式的值。
否则,对else表达式求值,并返回表达式的值。
if的else实参是选择性的,如果不写,默认是nil。 -
and宏
对任意参数进行逻辑与
判断。 -
or宏
对任意实参进行逻辑或
判断
6. 函数(Functions)
使用defun
定义新函数,Lisp不对程序(program)
、过程(procedure)
及函数(function)
做区别。
7. 递归(Recursion)
函数自己调用自己称为递归
。
-
eql
函数(谓词)测试两个实参是否相等 -
member
测试第一个实参(元素)是否为第二个实参(列表)中的成员。
8.阅读Lisp(Reading Lisp)
使用缩进来阅读及编写程序,不要只盯着括号看。任何好的编辑器都会又括号匹配的功能。
9. 输入输出(Input and Output)
-
format
格式化输出 -
read
标准输入函数
10. 变量(Variables)
let
可以创建新的局部变量
,在特定的上下文中使用。
let
表达式有两个部分
第一部分为一组创建新变量的指令,形式为(variable expression)。
第二部分为一个有表达式的函数体,在函数体内最后一个表达式的求值结果为let
的返回值。numberp
(谓词)用来测试实参是否为一个数字defparameter
用来创建一个全局变量
,全局变量的命名通常以开始,并以结束。defconstant
用来创建全局常量
。不需要给全局常量一个独一无二的名字,因为如果相同名字存在,会产生重复定义的错误。boundp
函数用来检查实参是否为全局变量
或全局常量
11. 赋值(Assignment)
-
setf
是赋值操作符,可以给全局或局部变量赋值。
setf
的第一个实参可以是表达式或变量名。
如果setf
的第一个实参是符号(symbol),并且不是某个局部变量的名字,则setf
会把这个符号设为全局变量。也就是隐式的创建全局变量。
但是还是推荐使用defparamter
明确地创建全局变量。
12.函数式编程(Functional Programming)
函数式编程意味着撰写利用返回值工作的程序,而不是修改程序中的某些东西。换言之,只使用函数的返回值,而不使用函数的副作用。
函数式编程允许使用交互式测试(interactive testing)
。
13.迭代(Iteration)
当想要重复做一些事情的时候,迭代比递归来的更自然。
-
do宏
是Common Lisp中最基本的迭代操作符。- 第一个实参是一组变量说明列表,(variable initial update)。
其中variable是符号,initial与update是表达式。变量会被赋予initial表达式的值 作为初始值。每一次迭代,都会被赋予update表达式的值。 - 第二个实参包含一个货多个表达式。其中,第一个表达式用来测试迭代是否结束。
而之后的表达式则被依次求值,直到迭代结束。其中最后一个值会被当作do的返回值返回。 - 其余的实参则组成了循环的函数体。每次迭代,都会被依序求值。
- 第一个实参是一组变量说明列表,(variable initial update)。
progn
接受任意数量的表达式,依序求值,并返回最后一个表达式的值。dolist
接受该形式的实参(variable expression),并在后边跟着一个具有表达式的函数主体。函数主体会被求值,而变量相继与表达式所返回的列表元素绑定。
14. 函数作为对象(Functions as Objects)
Lisp中,函数与符号、字符串一样是第一类对象。
function
特殊操作符返回接受实参关联的对象,缩写为#',称为升引号(sharp-quote)。
升引号后接函数名便可以将函数当作实参传给函数,function +
或#'+
。apply
接受一个函数和一个实参列表,并返回把传入函数应用到实参列表的结果。
apply
接受任意数量的实参,最后一个是列表
即可。funcall
与apply的效果相同,只是不需要把实参包装成列表。lambda
创建匿名函数使用 (funcall #'(lambda (x) (+ x 100)) 1)
15. 类型(Types)
在Common Lisp中,数值才是又类型的,而变量没有。
类型又fixnum
、integer
、rational
、real、number
、atom
和t
。
其中t是所有类型的基类(supertype)
。
-
typep
函数接收一个对象和一个类型,然后判断对象是否为该类型
习题(Exercises)
- (a) 14
- (b) '(1 5)
- (c)7
- (d)'(nil 3)
(cons 'a '(b c))
(cons 'a (cons 'b '(c)))
(cons 'a (cons 'b (cons 'c ())))
(defun our-get-fourth (lst)
(car (cdr (cdr (cdr lst)))))
(defun our-max (first-num second-num)
(if (> first-num second-num)
first-num second-num))
- (a)遍历x,遍历完毕后,返回一个nil
- (b)在y中查找x,如果找到,则返回元素在列表中的位置,如果没有找到则返回nil。
- (a)x为car就可以得到相同的结果
- (b)x为or可以得到相同的结果
- (c)x为apply可以得到相同的结果
(defun our-list-element (lst)
(if (null (car lst))
nil
(if (listp (car lst))
t
(our-list-element (cdr lst)))))
- (a)
- 递归版本
(defun print-point-rec (num) (if (zerop num) 'done (progn (format t "~A" ".") (print-point-rec (- num 1)))))
- 迭代版本
(defun print-point-ite (num) (do ((i num (- i 1))) ((zerop i) 'done) (format t "~A" ".")))
- 递归版本
- (b)
- 递归版本
(defun count-list-rec (lst) (if (null lst) 0 (+ 1 (count-list (cdr lst)))))
- 迭代版本
(defun count-list-ite (lst) (let ((len 0)) (dolist (obj lst) (setf len (+ 1 len))) len))
- 递归版本
(a)该函数接受一个数字列表是没问题,但是如果接受的是空列表或字符列表,则会报错。
(b)该函数没有结束条件,不论传入何种列表都会死循环。
更改后为
(defun summit-second (lst)
(let ((x (car lst)))
(if (null x)
0
(if (numberp x)
(+ x (summit-second (cdr lst)))
(summit-second (cdr lst))))))