您的位置:首页 > 其它

【战役总结】Common Lisp : A Gentle Introduction to Symbolic Computation

2013-08-17 20:23 483 查看
一.内容简介
整本书一共分成了14章,介绍了Common Lisp编程的入门级内容,虽然如此,整本书所介绍的内容是非常生动有趣的,且动手的练习也很多,每章都有Keyboard Exercise——就是那种需要你动手按照说明编写代码的大练习,比较有趣的几个键盘练习有:掷骰子游戏(第五章)、一个简单知识系统的模式匹配器(第七章)、家谱数据库(第八章)、一元函数画图程序(第九章)、经典的TIC-TAC-TOE游戏(第十章)、DNA含氮碱基匹配(第十一章)、判定网络(第十二章)、解密游戏(第十三章)和用宏实现的模拟有限状态机的简单DSL(第十四章)。
全书让我印象深刻的地方,除了大量的实践练习,就是Lisp语言带给我的编程的新感觉,贯穿全书的是各种丰富的图表,对各种概念进行了直观生动的讲解,以及Lisp语言基于REPL的交互式编程方式。第七章标题为“Applicative Programming”,其实它讲解了运用Lisp语言进行函数式编程的方式,不需要定义过多的变量,通过几个列表操作函数外加lambda表达式就能完成想要的工作,非常地便捷且具有很强的表达能力;第八章的递归讲得也非常精彩,通过一个虚构的“龙的故事”,将递归娓娓道来,深入浅出地教你如何用递归来思考问题,创造性地将递归思维分为三个步骤,以下是书中原话:
Three rules for solving problem recursively --- take every recursive problem as if it were a journey
1) Know when to stop;
2) Decide how to take one step;
3) Break the journey down into that step plus a smaller journey.
然后,将递归分为八种基本模式:(1)双测试尾递归、(2)单测试尾递归、(3)单测试增量递归、(4)列表构造递归、(5)多变量递归、(6)条件增量递归、(7)多重递归、(8)CAR/CDR递归;最后介绍了尾递归——一种对递归调用的优化方式,不过我感觉在其他语言中,编译器不一定会对其进行优化,但对函数式编程语言来说这个是必须的。这章节内容搭配上Lisp语言大道至简的表达能力,对递归的介绍出神入化,以至于学完之后用递归思考问题变成了一种很自然的思维方式,不过在日常工作学习中还需要多练习才是,我感觉这章和最后一章对宏的基础介绍是需要经常重新复习的内容。第十一章介绍的是迭代,也就是循环,这章介绍了Lisp语言的三种迭代结构:基本的dotimes和dolist,以及实现复杂功能的do循环。
最后要说的是第十四章,本章介绍了有关宏和编译的内容,Macro是Lisp语言区别于其他任何语言的强大功能,当然,这章对macro也只介绍了一些基础知识,但依靠一点点基础知识,已经能在键盘练习环节编写出一个简单的模拟有限状态机的领域特定语言了——尽管该语言只有两个表达式。读完本书,只是入门,要使用Lisp来编写大型的,复杂的软件还需要深入学习,不过不得不说的是,学习这本书的过程真是非常的有收获。接下来,应该好好读读《Practical
Common Lisp》、《ANSI Common Lisp》和《On Lisp》等书了。

二. 读书笔记及参考
"The greatest pleasure in programmig comes from gettng a lot done with code that simply and clearly expresses your intention."

"As we saw in the mini keyboard exercises, the way to solve any nontrivial

programming problem is to divide the problem into smaller, more manageable

pieces. This is done by writing and testing several simple functions, then

combining them to produce a solution to the main problem."

1. 关于Emacs

emacs 启动slime环境

M-x slime

缓冲区切换

C-x b

C-c C-c 将当前Lisp代码读入REPL环境

C-x C-s 保存文件

2. Common Lisp语言处理器分两部分——Reader和Evaluator

Reader:字符串转化为S-表达式

Evaluator:S-表达式转化为Lisp形式

S-表达式:列表和原子

原子:

1)number

2)string literal

以上自求值

3)symbol:求值为所代表的对象,T和NIL自求值,:开头的关键字符号自求值

*var*:全局变量

+var+:常量

Lisp 形式

原子

以符号开头的列表

function call forms:按顺序求值,然后作为参数传递给第一个符号代表的函数

macro forms:以S-表达式作为参数,返回的Lisp形式替代原有宏(宏扩展)

step 1:元素以未求值的形式传给宏函数

step 2:宏的展开式按照一般求值规则进行

special forms:按特殊规则进行求值,如IF,QUOTE

相等谓词

=:numbers

CHAR=:characters

EQ:object identity,two objects are EQ if they're identical

EQL:与EQ类似,但保证相同类型的数值或字符相等

3. Lisp代码风格参考

;;;; Four semicolons are used for a file header comment;

;;; A comment with three semicolons will usually be a paragraph

;;; comment that applies to a large section of code that follows

(defun foo (x)

(dotimes (i x)

;; Two semicolons indicate this comment applies to the code

;; that follows. Note that this comment is indented the same

;; as the code that follows

(some-function-call) ; this comment applies to this line only

(another i)))

4. 函数

定义形式:

(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))

传参形式(按顺序)

1)required

2)可选参数&optional

(defun foo (a b &optional c d) (list a b c d))

为可选参数提供默认值

(defun foo (a &optional (b 10)) (list a b))

可选参数的默认值可以通过其他参数计算得来

(defun make-rectangle (width &optional (height width)) ...)

使用多余变量判断可选参数的值是调用者传进来的还是默认提供的

(defun foo (a b &optional (c 3 c-supplied-p))

(list a b c c-supplied-p))

3)以列表形式出现的其余参数:&rest

提供可变参数机制(以列表形式存放)

(defun + (& rest numbers) ...)

4)关键字参数:&key

(defun foo (&key a b c) (list a b c))

(foo)

(foo :a 1)

(foo :a 1 :c 3)

为关键字参数提供默认值

(defun foo (&key (a 0) (b 0 b-supplied-p) (c (+ a b)))

(list a b c b-supplied-p))

在函数内为关键字参数提供不同参数名,可用于接耦公共API描述和具体实现,不常用

(defun foo (&key ((:apple a)) ((:box b) 0) ((:charlie c) 0 c-supplied-p))

(list a b c c-supplied-p))

5)辅助参数:&aux
&aux可用于定义辅助变量,它的作用相当于let,通过&aux可以避免在函数体中使用let绑定,如

(defun foo (x &aux (sx (sin x)) (i 0))

...)

相当于

(defun (x)

(let* ((sx (sin x))

(i 0))

...)

6)返回值

使用RETURN-FROM立即从函数返回

(defun foo (n)

(dotimes (i 10)

(dotimes (j 10)

(when (> (* i j) n)

(return-from foo (list i j))))))

7)关于函数式编程
函数是对功能的抽象,函数即数据——高阶函数

FUNCTION(#‘)特殊运算符可用于获得一个函数对象,传入函数名给它,返回该函数名代表的函数对象

FUNCALL和APPLY可以通过函数对象来调用函数

举例
(defun plot (fn min max step)

(loop for i from min to max by step do

(loop repeat (funcall fn i) do (format t "*"))

(format t "~%")))

(plot #'exp 0 4 1/2)

APPLY适用于运行时才知道实参列表的情形

(apply #'plot plot-data)

只要最后一个参数是列表,APPLY可以接受松实参

(apply #'plot #'exp plot-data)

8)匿名函数——lambda表达式
lambda表达式——匿名函数

(lambda (parameters) body)

lambda表达式的重要作用——构造闭包

functions that capture part of the environment where they're created

闭包将包含它的绑定形式中引入的词法域变量“捕捉”到函数体内,使得绑定形式之外仍然可以使用(秘诀在于b)

(defparameter *fn-list*

(let ((count 0))

(list

#'(lambda () (incf count))

#'(lambda () (decf count))

#'(lambda () count))))

5. 变量

1)定义新变量

defun 和 let被称为绑定形式,所引入的新变量被局限于此绑定形式之内,称为“词法域变量”

函数中绑定参数 defun

参数绑定对象的引用,因此函数内对对象的改变会影响到函数外

LET 特殊运算符

(let (variable*)

body-form*)

每个variable都是一个变量初始化形式

举例:

(let ((x 10) (y 20) z)

...)

let的变种let*可以使用已绑定的变量初始化新变量,let不行,因为let对变量并行绑定,而let*串行绑定

2) Lisp变量

(1)关于变量的域——词法域和动态域

动态域变量通常被称为special variables

动态域变量并不局部于任何函数,它的值是任何地方都可访问的,词法域变量只能在定义它的形式体内访问。

动态域的变量可以被随时重新绑定,当离开那个绑定范围后,它又自动恢复到原来的值

defvar defparameter和defconstant

三者都可用于定义特殊变量

defvar用于定义其值会在程序执行时发生改变的变量;defvar不会改变已经存在的特殊变量的值

defparameter用于定义其值不会改变的变量;defparameter会改变已经存在的特殊变量值

defconstant用于定义常量,且命名约定为前后+号

将动态变量使用于绑定形式可以让全局绑定被暂时隐藏起来,赋予动态变量新的词法域含义

(let ((*standard-output* *some-other-stream*))

(stuff))

使得*standard-output*在此let绑定形式内被暂时赋值为*some-other-stream*,因此在这个绑定形式内所有的函数,所有这些函数调用的其他函数都会受它影响;词法域的绑定只能在绑定形式的词法范围被引用,无法更深一层次,也就是说词法域函数调用的其他函数并不接受此改变。

(2)赋值操作
setf

(setf place value)

(setf x 1 y 2)

(setf x (setf y (random 10)))

变量自增自减

(incf x)

(decf x)

(incf x 10)

变量交换

(rotatef x y)

移形换位

(shiftf a b 10)

b赋值给a,10赋值给b

自增/自减 incf/decf

列表当堆栈使用pop/push

(push 'dish1 mystack)

6.标准宏构造

1)when 当条件为真时求值,unless相反,条件求值语句,可求值多条

(when (spam-p current-message)

(file-in-spam-folder current-message)

(update-spam-db current-message))

2)cond宏:类swith case

(cond

(test-1 form*)

(test-2 form*)

.

.

.

(test-N form*))

(cond (a (do-x))

(b (do-y))

(c (do-z)))

3)and, or 和 not

短路求值

4)循环

(1)dotimes/dolist

(dotimes (index-var n [result-form]

body)

return 语句可以立即从迭代中返回值

(dolist (x '(1 2 3)) (print x))

(dotimes (i 4) (print i))

(2)do 循环

特性:

绑定任意数量变量;

完全控制变量的变化;

自定义结束循环的条件;
(do ((var1 init1 [update 1])

(var2 init2 [update 2])

...)

(test action1 ... action-n)

body)

一开始,所有的变量被赋值为各初始值,然后test被求值,若为真,则action被依次求值并以最后一个作为返回值;否则若test为假,则body被求值,第二次迭代时所有的变量被赋值为update,若update省略,则对应变量的值在循环过程中保持不变
求Fib(11)
(do ((n 0 (1+ n))

(cur 0 next)

(next 1 (+ cur next)))

((= 10 n) cur))

(3)loop 循环

(loop

body-form*)

(loop

(when (> (get-universal-time) *some-future-date*)

(return))

(format t "Waiting...~%")

(sleep 1))

(loop for i from 1 to 10 collecting i) ---> (1 2 3 4 5 6 7 8 9 10)

7.自定义宏构造
1)过程

Macro : the code that generates code

macro expansion time

runtime

原型

(defmacro name (parameter*)

"Optional documentation string."

body-form*)

编写宏的关键是:你要知道自己从哪里来,要到哪里去

(1) Write a sample call to the macro and the code it should expand into, or vice versa;

(2) Write code that generates the handwritten expansion from the arguments in the sample call;

(3) Make sure the macro abstraction doesn't leak.

macro, function和special function的区别
(1)函数总是对实参求值,而宏总是不对实参求值 ;
(2)函数可以返回任意值,宏必须返回合法的lisp表达式;
(3)宏返回的表达式被立即求值,而函数的返回值不求值;
特殊函数是固定的,程序员无法自定义,特殊函数不求值实参,但也不返回表达式以供求值,它们是底层函数,是lisp的基石。

2)反引号的作用——函数模版
跟quote一样,反引号用于引用列表字面值,但反引号列表中以逗号开头的表达式会被求值
(setf name 'fred)
`(this is ,name from guiyang) 求值得到(this is fred from guiyang)

宏定义中最常见的一种模式是避免函数调用时对实参加引号,而是使用',var来得到实参名
(defmacro two-from-one (func object)
`(,func ',object ',object))
将实参名作为参数传递给func函数

3)使用,@进行拼接,结合
,@后跟列表,宏扩展时将此列表的第一层括号打开并拼接进去
(setf address '(16 maple drive))
`(,name lives at ,@address) 求值为 (FRED LIVES AT 16 MAPLE DRIVE)
经典例子:编写一个函数set-zero,该函数接受任意数量参数,并将它们全部设置为0
(defmacro set-zero (&rest variables)

‘(progn ,@(mapcar #’(lambda (var)

(list ’setf var 0))

variables)

’(zeroed ,@variables)))

4)宏的&body参数
宏的&body参数类似于函数的&rest,但它特别声明了其后的参数是某个控制结构的语句体,比如可以使用宏来定义新的语法——类似于其他语言中的while控制结构
(defmacro while (test &body body)
‘(do ()
((not ,test))
,@body))

使用该控制结构

(defun next-power-of-two (n &aux (i 1))

(while (< i n)

(format t "~&Not ~S" i)

(setf i (* i 2)))

i)

5)宏参数的解构(destructuring)

因为宏并不求值输入参数,因此如果我们将宏参数替换为列表结构,那么宏会模式匹配该表达式并自动结构

比如这样的函数

(defmacro mix-and-match (p q)

(let ((x1 (first p))

(y1 (second p))

(x2 (first q))

(y2 (second q)))

‘(list ’(,x1 ,y1)

’(,x1 ,y2)

’(,x2 ,y1)

’(,x2 ,y2))))

就可以充分利用宏的解构特性,改写为:

(defmacro mix-and-match ((x1 y1) (x2 y2))

‘(list ’(,x1 ,y1)

’(,x1 ,y2)

’(,x2 ,y1)

’(,x2 ,y2)))

8. 数值

二进制:#B/#b,如#b10101八进制:#O/#o,如#o777

十六进制:#X/#x,如#x3F

#nR:n进制(2~36),如#36R

数值函数

+ - * /

mod/rem floor truncate

自增自减:incf/decf

数值比较

= /= > >= < <=

最值:min/max

判断正负零:zerop plusp minusp

判断奇偶:evenp oddp

9.字符

#\加字符,如字符x表示为#\x

特殊字符:#\Space #\Newline

字符比较

CHAR= CHAR-EQUAL(大小写不敏感)

CHAR/= CHAR-NOT-EQUAL

CHAR< CHAR-LESSP

CHAR> CHAR-GREATERP

CHAR<= CHAR-NOT-GREATERP

CHAR>= CHAR-NOT-LESSP

10. 字符串

STRING= STRING-EQUAL(大小写不敏感)

STRING/= STRING-NOT-EQUAL

STRING< STRING-LESSP

STRING> STRING-GREATERP

STRING<= STRING-NOT-GREATERP

STRING>= STRING-NOT-LESSP

*注:只能比较两个字符串,可以用:start1 :end1 :start2 :end2(左闭右开)来指定比较的子串,除STRING= STRING-EQUAL外其他函数返回两字符串第一个不相同的位置,如 (string< "lisp" "lisper") --> 4

判断是否是字符串

stringp

11. Common Lisp I/O

1)format

~%:普通换行符

~&:若已经处于行首则不换行

~S:打印Lisp对象(转义字符)

~A:同上,但不转义字符

~D:打印十进制

~M,NF:打印浮点数,该数占用M个空间,小数点显示N位

2)Lisp读写文件

(with-open-file (var pathname)

body)

写文件

(with-open-file (stream "/usr/dst/timber.newdat" :direction :output)

...)

read处理end-of-file的情况

(read nil eof-indicator )

eof-indicator 可用 $eof$

3)Lisp日志记录器

(dribble "session1.log")

;;;之间的交互信息都会被保存到上面这个日志文件中

(dribble)

12.几个有用的函数
1)sublis函数:用alist替换列表对应值

举例

(sublis '((x . "X")

(o . "O " )

(nil . " "))

'(x o nil x))

2)(nthcdr n list)

对列表使用n次cdr

3)隐式代码块 implicit blocks

函数体可以看作blocks,其中函数名即block name,return-from可用于从block中退出

(return-from block-name value)

4)prog1,prog2和progn
求值代码块,但分别以第一个,第二和和最后一个语句的求值作为返回值

13. 类型和结构
typep用于判断某对象是否为某种类型
(typep 3 'number)
(typep 'foo 'symbol)

type-of用于返回某 对象的类型说明符
defstruct 宏用于构造结构,之后自动产生一系列函数,结构用#S(...)表示
(defstruct starship

(name nil)

(speed 0)

(condition 'green)

(shields 'down))
make-starship 可通过关键字参数进行定制实例化
starship-p
starship-speed 访问成员

14. 数组,散列表和属性表
1)数组用#(...)表示,该表示法可用于setf中直接创建数组,可用aref访问数组元素
(setf arr '#(tuning violin 440 a))
(aref arr 2)
(setf (aref arr 2) 'foo)
创建数组函数make-array
(make-array 5 :initial-element 1)
(make-array 5 :initial-contents '(a e i o u))

2)散列表
与邻接表的功能相同,但其使用的散列算法能比邻接表查找要快很多倍,而后者直观易懂,前者则依赖于具体实现,因此该选用哪个就要以速度或简洁性作为评判标准
只能使用make-hash-table来创建散列表,缺省使用EQL作为比较函数,使用gethash配合setf进行读写
(setf (gethash 'john h) '(attorney (16 maple drive)))
(gethash 'john h)

3)属性表
与邻接表和散列表功能相同,它被组织为键值交替的列表,可用get配合setf进行读写
(ind-1 val-1 ind-2 val-2 ind-3 val-3 ...)
注意,get可接受第三个参数作为其找不到对应值时的缺省返回值
函数symbol-plist返回符号的属性表
函数remprop可用于删除符号中的某个属性

4)coerce函数 可以将序列从一种类型转换为另一种类型
(coerce "Cockatoo" 'list)
(coerce '(#\b #\i #\r #\d) 'string)
(coerce '(foo bar baz) 'vector)

5)使用make-array函数来生成字符串
(make-array 3 :element-type 'string-char :intial-contents '(#\M #\o #\m))
"Mom"

6)函数map,find-if,reduce可用于任意序列
(map 'list #'+ ; 第一个参数表示返回的序列类型(列表)
'(1 2 3 4)
'(10 20 30 40))

==>(11 22 33 44)

15. 递归模式
1)双测试尾递归
(defun func (x)
(cond (end-test-1 end-value-1)
(end-test-2 end-value-2)
(t (func reduced-x))))

ex
(defun anyoddp (x)
(cond ((null x) nil)
((oddp (first x)) t)
(t (anyoddp (rest x)))))

2)单测试尾递归

(defun func (x)
(cond (end-test end-value)
(t (func reduced-x))))

ex
(defun find-first-atom (x)
(cond ((atom x) x)
(t (find-first-atom (first x)))))

3)单测试增量递归
(defun func (x)
(cond (end-test end-value)
(t (aug-fun aug-val
(func reduced-x)))))

ex
(defun count-slices (x)
(cond ((null x) 0)
(t (+ 1 (count-slices (rest x))))))

4)列表构造递归
(defun func (n)
(cond (end-test nil)
(t (cons new-element
(func reduced-n)))))

ex
(defun laugh (n)
(cond ((zerop n) nil)
(t (cons 'ha (laugh (- n 1))))))

5)多变量递归
(defun func (n x)
(cond (end-test end-value)
(t (func reduced-n reduced-x))))

ex
(defun my-nth (n x)
(cond ((zero p) (first x))
(t (my-nth (- n 1) (rest x)))))

6)条件增量
(defun func (x)
(cond (end-test end-value)
(aug-test (aug-fun aug-val
(func reduced-x))
(t (func reduced-x))))

ex
(defun extract-symbols (x)
(cond ((null x) nil)
((symbolp (first x))
(cons (first x)
(extract-symbols (rest x))))
(t (extract-symbols (rest x)))))

7)多重递归
(defun func (n)
(cond (end-test-1 end-value-1)
(end-test-2 end-value-2)
(t (combiner (func first-reduced-n)
(func second-reduced-n)))))

(defun fib (n)
(cond ((equal n 0) 1)
((equal n 1) 1)
(t (+ (fib (- n 1))
(fib (- n 2))))))

8)CAR/CDR 递归(用于树形遍历)
(defun func (x)
(cond (end-test-1 end-value-1)
(end-test-2 end-value-2)
(t (combiner (func (car x))
(func (cdr x))))))

(defun find-number (x)
(cond ((numberp x) x)
((atom x) nil)
(t (or (find-number (car x))
(find-number (cdr x))))))

8) 尾递归
尾递归技术
尾递归技术可以消除递归调用之间的“依赖性”,使得空间复杂度为O(1)
尾递归四大特征:
(1)绝大多数尾递归为“主函数+helper”形式,通过帮助函数实现递归主体;
(2)使用额外的变量在递归调用之间传递计算的中间结果;
(3)递归分支是纯的,不包含其他运算;
(4)非递归分支返回的是中间计算的结果;
9) 破坏性列表操作
(1)NCONC ——破坏型append
(2)NSUBST——破坏型subst
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: