Pulpcode

捕获,搅碎,拼接,吞咽

0%

实用common lisp 教程 读书笔记3

Start

最近一直在阅读《如何阅读一本书》,突然有了一个新想法–用新的方式写读书笔记。

虽说我原来也写过读书笔记,但基本是把书中的一些重点总结出来,或者是摘抄一些经典的句子,从来没想过将一本书的阅读过程写成博客,不是简单的读书思考,发表自己的看法,而是去抓住作者的意图,像是批注,又像是总结,同时又将自己已积累的知识串入。这对自己文笔也是一次锻炼-能够把问题描述清楚不容易

之所以这样,是为了使用《如何阅读一本书》的读书技巧,理解作者的意图。

第三章: 简单的数据库

这一章,作者通过实现了一个能够增删改查的单表数据库,来介绍common lisp的一些特性和优点。

值得一提的是,作者在这一章就介绍了不少lisp知识,而且每当引入新的lisp知识时,作者会写这样一句话:”目前我不会深入讨论关于这一符号的所有细节”。可以看出,作者是一个实用主义,不会先空谈大量的理论。

作者先定义,将一条CD记录用如下的方式表示和存储:

1
(list :title title :artist artist :rating rating :ripped ripped)

创建一条记录和插入一条记录的方式都比较简单,为了让打印出来的文章变得漂亮,作者在之后的篇幅里介绍了Format和一些格式化字符串的方式。
我觉得这些都不算是重点,即使语法上不同,其本质思想也与其它语言,比如C的printf之类的格式化字符串类似。

之后加入了交互式的输入方式。引入了loop宏,来让用户多次输入。

loop宏配上if return类似于while if break

and or 也与python类似,都是短路求值,都是返回最后一个符合的布尔表达式,只不过语法不同。

之后为了持久化,引入了读写文件

这里有个有趣的地方是,lisp可以直接将这个列表写入文件,然后读取出来,它不需要像其它语言,比如python那样,将一个对象序列化之后,才能写入文件。

接下来作者引入了查询:

1
(remove-if-not #'(lambda(x)(= 1 (mod x 2))) '(1 2 3 4 5 6 7 8 9))

这样的方式是告诉lisp,将这个符号当作一个函数,有点类似于其它语言中的函数对象。

这种风格,应该是函数式的编程,就像是python中的filter:

1
filter(lambda x: x%2 == 0, range(1, 11))

首先,这是一个通过artist查找的select函数:

1
2
3
4
(defun select-by-artist (artist)
(remove-if-not
#'(lambda (cd) (equal (getf cd: artist) artist))
*db*))

很明显,你不得不为每一个字段写一个单独的select函数,比如select-by-title,select-by-rating

为了防止重复,作者进行了第一次抽象,

1
2
(defun select (select-fn)
(remove-if-not select-fn *db*))

具体的选择函数在根据不同的字段去实现。
这其实很像是面向对象中的定义接口。然后在根据接口做不同的实现。

虽然要写的代码比原来少了,但是你还是要为不同的字段实现不同的选择器,比如

1
2
(defun atrist-selector (artist)
#'(lambda (cd) (equal (getf cd :artist) artist)))

接下来,作者实现了一个函数,能够根据参数生成选择器。
也就似乎where函数的实现:

where的第一次实现

其基本思路就是用and,将传入参数的字段拼接起来,没有传入参数的,就用t替换。

这种方法其实没什么高深的,在python中,用闭包将选择生成的lamaba表达式放入一个list,一样可以做得到。

主要是因为lisp的所有代码都是list表达式,所以看着短,写起来也自由的多。

update的引入

因为有了之前的铺垫,所以update看上去也没那么复杂。

通过mapcar(有点类似于python中的map),对每一个行执行where函数,如果为真,就通过传入的参数,执行相应的更新。

有了select,delete本身就简单多了:

1
2
(defun delete-rows (selector-fn)
(setf *db* (remove-if selector-fn *db*)))

接下来介绍的部分,基本算是这一章的亮点了。

这也是lisp的精髓之一,宏:

新版的where

原来的where,还是沉余,因为你要在运行期间,检查某个参数是否传了进来,而且如果我们加入了新的字段,也要对where和updater进行大量的修改。

作者用一个reverse的列子,介绍了宏,这个例子没有多大意义,就连作者都称其为“简单而荒唐”的例子。

作者在这里为宏下了一个定义:“一个由编译器自动为你运行的代码生成器”。

也就是说,我们可以通过宏,将这段代码:

1
(where :title "Give Us a Break" :ripped )

直接”变为”如下代码:

1
2
3
#'(lambda (cd)
(and (equal (getf cd :title) "Give Us a Break")
(equal (getf cd :ripped) t)))

先是:

1
2
(defun make-comparison-expr (field value)
(list 'equal (list 'getf 'cd field) value))

这里引入了反引号,进行代码精简。

1
2
(defun make-comparison-expr (field value)
`(equal (getf cd, field), value))

它可以通过传入的字段,生成语句。
之后是loop宏,它将传入的参数两个两个的传入到 make-comparison-expr中,将结果保存到一个list中:

1
2
3
(defun make-comparisons-list (fields)
(loop while fields
collecting (make-comparison-expr (pop fields) (pop fields))))

之后才是where宏,它将上面函数返回的list用and拼接。

1
2
(defmacro where (&rest clauses)
`#'(lambda (cd) (and ,@(make-comparisons-list clauses))))

这里引入了两个符号:@和&rest:

  1. @用来展开一个list,
  2. &rest,类似于python中的*args,

感想:

  1. 说实话,lisp的语法有些太“疯狂”了,不知道要花多长时间才能够真正理解。
  2. 但是我确实可以细微的体会到lisp的强大之处。
  3. 我有一种预感,但是不知道对不对。即:优化,精简代码的大多数场景,都是将if等逻辑判断,改为数据结构驱动。