细心的人应该会发现,在第三章的最后,作者只将where函数改成了宏,而update函数仍然是带有重复代码:
(defun update (selector-fn &key title artist rating (ripped nil ripped-p))
(setf *db*
(mapcar
#'(lambda (row)
(when (funcall selector-fn row)
(if title
(setf (getf row :title) title))
(if artist
(setf (getf row :artist) artist))
(if rating
(setf (getf row :rating) rating))
(if ripped-p
(setf (getf row :ripped) ripped)))
row)
*db*)))
重复代码集中在不必要的判断当中,作为课后练习,我们这就来解决它。
首先,我们要写一个函数,它接受两个参数 field 和 value,作为宏的辅助函数,用于修改 cd 数据,这一函数和书中的 make-comparsion-expr 很相似,因此我称之为 make-update-expr:
(defun make-update-expr (field value)
`(setf (getf row ,field) ,value))
嗯,看上去不错,在REPL中试试:
[2]> (make-update-expr :title "hello")
(SETF (GETF ROW :TITLE) "hello")
测试成功!接下来,我们还需要一个像where函数的辅助函数 make-comparisons-list 那样,从一个列表中提取多个 field-value 对并将参数传给 make-update-expr 的函数,我称之为 make-updates-list :
(defun make-updates-list (fields)
(loop while fields
collecting (make-update-expr (pop fields) (pop fields))))
上面的函数遍历 fields 列表,从中提取 field-value 对,并生成宏所需的表达式。
再在REPL中试试它:
[4]> (make-updates-list *test-field-value-list*)
((SETF (GETF ROW :TITLE) "hello")
(SETF (GETF ROW :ARTIST) "huangz"))
一切顺利,是时候让新的 update 函数登场了:
(defmacro update (selector-fn &rest clauses)
`(setf *db*
(mapcar
#'(lambda (row)
(when (funcall ,selector-fn row)
,@(make-updates-list clauses))
row)
*db*)))
新的 update 函数和它的哥哥很相似,主要区别在于参数部分,将原本的 title、artist 之类的替换成了 &rest clauses ,可以接受不定数目的参数。
另外,在函数的中间,我们将原本重复的代码换成了 ,@(make-updates-list clauses) ,这行代码在宏执行的时候就会被展开,生成我们需要的代码,不多也不少。
最后,现在这个无论 cd 的数据域怎么变,这个update都不用修改,因为它足够通用,这就是新版的 update 对比旧版 update 的另一个显著的好处。
再次拜访我们的老朋友REPL,先展开宏看看:
[5]> (macroexpand-1 '(update (where :artist "huangz") :rating 10 :ripped t))
(SETF *DB*
(MAPCAR
#'(LAMBDA (ROW)
(WHEN (FUNCALL (WHERE :ARTIST "huangz") ROW)
(SETF (GETF ROW :RATING) 10)
(SETF (GETF ROW :RIPPED) T))
ROW)
*DB*)) ;
核心部分是两个 SETF 语句,它们是通过宏生成的,嗯,看上去如我们意料一样,现在,用数据测试下:
[11]> (dump-db)
TITLE: world
ARTIST: huangz
RATING: 8
RIPPED: T
TITLE: hello
ARTIST: huangz
RATING: 3
RIPPED: NIL
NIL
[12]> (update (where :artist "huangz") :rating 10 :ripped t)
((:TITLE "world" :ARTIST "huangz" :RATING 10 :RIPPED T) (:TITLE "hello" :ARTIST "huangz" :RATING 10 :RIPPED T))
[13]> (dump-db)
TITLE: world
ARTIST: huangz
RATING: 10
RIPPED: T
TITLE: hello
ARTIST: huangz
RATING: 10
RIPPED: T
NIL
测试成功,我们更新了两张 artist 为 "huangz" 的唱片的 rating、ripped 数据域。
嗯。。。看上去这道课后练习还暂时未能结束,因为在新的 update 函数和新的 where 的辅助函数中,我们看到了新的重复代码:
; where helper
(defun make-comparison-expr (field value)
`(equal (getf cd ,field) ,value))
(defun make-comparisons-list (fields)
(loop while fields
collecting (make-comparison-expr (pop fields) (pop fields))))
; update helper
(defun make-update-expr (field value)
`(setf (getf row ,field) ,value))
(defun make-updates-list (fields)
(loop while fields
collecting (make-update-expr (pop fields) (pop fields))))
哈,问题就在这,让我们继续消灭重复,将抽象(脑抽筋)进行到底。
很明显, make-comparison-expr 和 make-update-expr 只有一个函数(equal和setf)之差,我们可以抽象出一个 make-some-expr 函数或宏,但是,慢着,因为它们都是辅助函数,所以在它们的体内,有各自的自由变量,分别是 row 和 cd,这样一来,虽然我们可以继续对它们两进行抽象,但是,我想,这意义不大,说不定将来的麻烦会比好处多。
因此,我决定将目标移向 make-comparisons-list 和 make-updates-list ,这两个函数体内没有自由变量,而且区别只在于一个函数之差,我们两三下功夫就能抽象出一个新的通用函数:
(defun make-func-lists (func fields)
(loop while fields
collecting (funcall func (pop fields) (pop fields))))
测试一下,你就知道:
; 测试 make-update-expr
[5]> (defvar *test-list-1* (list :artist "huangz" :rating 5))
*TEST-LIST-1*
[11]> (make-func-lists #'make-update-expr *test-list-1*)
((SETF (GETF ROW :ARTIST) "huangz") (SETF (GETF ROW :RATING) 5))
; 测试 make-comparison-expr
[12]> (defvar *test-list-2* (list :title "hello" :ripped t))
*TEST-LIST-2*
[13]> (make-func-lists #'make-comparison-expr *test-list-2*)
((EQUAL (GETF CD :TITLE) "hello") (EQUAL (GETF CD :RIPPED) T))
嗯,以下就是我们新的 make-comparisons-list 和 make-updates-list 函数了:
(defun make-updates-list (fields)
(make-func-lists #'make-update-expr fields))
(defun make-comparisons-list (fields)
(make-func-lists #'make-comparison-expr fields))
在实际数据中测试一下子:
[3]> (dump-db)
TITLE: world
ARTIST: huangz
RATING: 8
RIPPED: T
TITLE: hello
ARTIST: huangz
RATING: 3
RIPPED: NIL
NIL
[4]> (where :rating 8)
#<FUNCTION :LAMBDA (CD) (AND (EQUAL (GETF CD :RATING) 8))>
[5]> (where :title "hello" :rating 8)
#<FUNCTION :LAMBDA (CD) (AND (EQUAL (GETF CD :TITLE) "hello") (EQUAL (GETF CD :RATING) 8))>
[6]> (select (where :rating 3 :artist "huangz"))
((:TITLE "hello" :ARTIST "huangz" :RATING 3 :RIPPED NIL))
[7]> (update (where :rating 3 :artist "huangz") :title "He!!0" :rating 5)
((:TITLE "world" :ARTIST "huangz" :RATING 8 :RIPPED T) (:TITLE "He!!0" :ARTIST "huangz" :RATING 5 :RIPPED NIL))
[8]> (dump-db)
TITLE: world
ARTIST: huangz
RATING: 8
RIPPED: T
TITLE: He!!0
ARTIST: huangz
RATING: 5
RIPPED: NIL
NIL
嗯,我们先是用宏改写了 update 函数,然后通过新的函数 make-func-lists,将 make-updates-list 和 make-comparisons-list 函数改头换面。
我们的数据库程序在减少重复代码方面又达到了一个新的高度,抽象层次直逼外太空,我几乎快要缺氧了。阿阿阿。。。
----------
;;;; 新代码片段,其余代码和书中的一样
;;;
(defun make-func-lists (func fields)
(loop while fields
collecting (funcall func (pop fields) (pop fields))))
;;; where function
(defun make-comparison-expr (field value)
`(equal (getf cd ,field) ,value))
(defun make-comparisons-list (fields)
(make-func-lists #'make-comparison-expr fields))
(defmacro where (&rest clauses)
`#'(lambda (cd)
(and
,@(make-comparisons-list clauses))))
;;; update function
(defun make-update-expr (field value)
`(setf (getf row ,field) ,value))
(defun make-updates-list (fields)
(make-func-lists #'make-update-expr fields))
(defmacro update (selector-fn &rest clauses)
`(setf *db*
(mapcar
#'(lambda (row)
(when (funcall ,selector-fn row)
,@(make-updates-list clauses))
row)
*db*)))
相关推荐
标题:“实用Common.Lisp编程.pdf” 描述:“实用Common.Lisp编程.pdf,2011.10出版” 从这些信息中,我们可以提炼出几个关键的知识点: ### Common Lisp语言简介 Common Lisp是一种高级的、通用的、多范式的编程...
Common Lisp是一种历史悠久的编程语言,属于Lisp语言的一个重要分支。它以其强大的功能、灵活的语法和高效的执行能力而著称。Common Lisp语言不仅在学术领域内有广泛应用,同样在工业界也颇受青睐,特别是在人工智能...
本书首先从作者的学习经过及语言历史出发,随后用21个章节讲述了各种基础知识,主要包括:REPL 及Common Lisp 的各种实现、S- 表达式、函数与变量、标准宏与自定义宏、数字与字符以及字符串、集合与向量、列表处理、...
这本《Practical Common Lisp》之所以号称Practical,正是因为这本书大量介绍Common Lisp在现实世界中的各种应用方式,算是第一本「入世传教」的Common Lisp著作。《Practical Common Lisp》是目前最畅销的Common ...
### 实用Common Lisp编程 #### Lisp语言概述 Lisp(LISt Processing language)是一种历史悠久的高级编程语言,自1958年诞生以来,在计算机科学领域有着不可替代的地位。它以其独特的列表结构、灵活的语法以及强大...
从资源的目录来看,第一章到第三章讲述了 Lisp 语言的基础知识,包括列表、特殊数据结构和控制流程。第四章到第六章讲述了函数、输入与输出、符号和数字等基本概念。第七章到第九章讲述了宏、Common Lisp 对象系统和...
个章节讲述了各种基础知识,主要包括:REPL 及Common Lisp 的各种实现、S- 表达式、函数与变量、标 准宏与自定义宏、数字与字符以及字符串、集合与向量、列表处理、文件与文件I/O 处理、类、FORMAT 格式、符号与包,...
3. **函数与过程**:Lisp中的函数是第一类对象,可以作为参数传递,也可以作为返回值。书中将涵盖函数定义、匿名函数(lambda表达式)、高阶函数和递归函数的使用。 4. **宏系统**:Common Lisp的宏系统是其独特...
Common Lisp,缩写为 CL(不要和缩写同为CL的组合逻辑混淆),是Lisp的众多方言之一,标准由ANSI X3.226-1994定义。它是为了标准化此前众多的Lisp分支而开发的,它本身并不是一个具体的实现而是各个Lisp实现所遵循的...
本压缩包包含三本关于Common Lisp的重要书籍,分别是《ANSI Common Lisp》、《On Lisp》以及《实用Common Lisp编程》,都是学习和深入理解Common Lisp的宝贵资源。 《ANSI Common Lisp》由Paul Graham撰写,是...
《实用Common Lisp》是Apress出版社出版的一本深入探讨Lisp编程语言的书籍,特别强调其实用性。这本书以其精细的页面设计和清晰的印刷质量,非常适合打印阅读,以便于深度学习和理解Lisp的强大之处。 Lisp是一种...
这是LISP和Common LISP编程的上一页,我们正在处理中,将那里的所有书籍都转换为新页面。 请再次检查此页面!!!
《ANSI Common Lisp》作为一本详尽介绍Common Lisp编程语言的经典著作,对于初学者来说是非常宝贵的资源。本书的中文翻译版本不仅包含了原书的所有内容,还针对中文读者进行了细致的编排和调整,使得读者能够更好地...
3. **符号和原子性**:在Common Lisp中,符号是第一类对象,且不可变,这意味着它们可以被用作变量、函数名等。 4. **列表和S-表达式**:Common Lisp的基础数据结构是列表,S-表达式(Symbolic Expression)是其...
Common Lisp的标准库极其庞大,包含了各种实用的模块和函数,如数据结构操作、文件系统接口、网络通信、并行计算和数据库访问。书中会详尽阐述这些库的使用,帮助读者快速融入Common Lisp的生态环境。 总的来说,...
Common Lisp Koans(lisp-koans)是一个语言学习练习程序,类似 ruby koans,python koans 等等。Common Lisp Koans 主要是帮助学习一些 lisp 规范特性和改进,可以学习到大量的 Common Lisp 语言特性。终端,在文件...