按照作者的说法,他准备介绍三种编程风格,首当其冲的就是本章了,本章的题目叫做Applicative Programming,直译叫“应用式编程”,网上查了,其实就是“函数式编程"的同义词,猜想Applicative Programming,想强调的是Applicative——高阶函数(high order function)用其他的函数和列表作为参数传入,并用这个传入的函数对列表进行计算然后得到一个结果。
一. 几个常用的built-in Common Lisp高阶函数
Common Lisp内建的几个高阶函数(书中叫Applicative Operator)有:
1. mapcar
从函数名就能很好地理解它的作用,调用该函数需要传递函数和列表作为参数,然后该函数依次对列表中每个元素(顶层元素,不解析嵌套列表)调用该函数,然后将得到的结果集拼接成列表输出,所以叫map(映射),比如,对(1 2 3 4 5)每个元素求平方,如果用C语言写可能会是这样:
for (i = 0; i < sz; i++)
result[i] = input[i] * input[i];
而Lisp写出来则是:
(mapcar #’(lambda(n) (* n n)) ’(1 2 3 4 5))
这里用了lambda表达式来编写一个平方函数,当然也可以另外定义一个square然后传递给mapcar,mapcar不是只能操作一个列表,实际上我们可以传递多个列表进去,而相应的函数就要变为多参数函数或多元运算符,(mapcar #'+ '(1 3 5 7 9) '(2 4 6 8 10))可以将两个列表对应相加的结果返回。
2. find-if
参数为函数fn和列表lst,返回的结果为lst中第一个使fn为真的函数,可以带可选参数:from-end让find-if从后向前查找。
3. remove-if和remove-if-not
传入的参数为函数fn和列表lst,remove-if将列表中所有使函数fn为真的元素删除,而remove-if-not将使函数fn为真的元素保留,其他的删除。
4. reduce
归约函数,对一个列表进行操作,把列表中的元素用传入的函数参数进行计算,然后把计算得到的一个结果值返回,比如多个列表归并成一个:
(reduce #'append '(a b c) '(1 2 3) '(a a a))
得到 '(a b c 1 2 3 a a a)
5. every
若传入的列表中每个元素都使参数函数fn为真,则every返回true,否则返回false
6. lambda表达式
用途很多,其中的一个用途就是与高阶函数搭配使用,在调用上述高阶函数时,常常不用单独定义一个命名函数,而是在高阶函数参数传递中随手写一个lambda表达式,之后该表达式作为匿名函数参数传入高阶函数,完成所需要的计算,使用lambda表达式常常使得程序紧凑,易读。
7. “function”特殊函数
若传递一个符号给该函数,则函数返回该符号代表的函数对象(Lisp中“符号”是一种数据类型,它可以表示某变量,可以表示某函数);若传递给它lambda表达式,则它返回该表达式对应的函数对象(词法闭包),一般用#'来表示,它使得函数在Lisp语言中成为“一等公民”——作为参数传递或作为结果返回。
(1)作为参数传递
这种情况常见于各种高阶函数的调用中,如果需要自己实现一个高阶函数,也要用到它,如:
(defun rights (fn)
(funcall fn '(life is like a battle)))
;调用(rights #'length)得到5
这里要注意的是变量fn的值是一个函数,但它自己不是函数名,因此(fn '(life is like a battle))是错误的,必须用funcall来调用fn绑定的那个函数对象
(2)作为结果返回
书上的例子,实现一个高阶函数,传入一个数字n,返回一个判断参数值是否大于n的谓词函数
(defun make-greater-than-predicate (n)
#'(lambda (x) (> x n)))
该函数将返回一个谓词函数fn,fn接受一个参数x,并当x > n 时返回true,比如(make-greater-than-predicate 4)将返回一个谓词函数判断其参数是否大于4,那么
(find-if (make-greater-than-predicate 3) '(2 3 4 5 6 7 8 9))将返回第一个比3大的数4
8. trace和untrace工具
trace可以用来跟踪函数的执行,但是要注意,跟踪内建函数有时候可能会导致死循环的发生。
最后,给出一段Lisp程序,该程序是本章的大练习,实现了一个简单的知识系统,该知识系统用数据库表示砖块的颜色,大小,形状和位置等信息,然后用一个简单的模式匹配器去试图从中获取知识,比如模式’(b1 ? b2)试图找出b1和b2的关系,'(b1 color ?)试图查找b1的颜色信息。
(defparameter *database*
'((b1 shape brick)
(b1 color green)
(b1 size small)
(b1 supported-by b2)
(b1 supported-by b3)
(b2 shape brick)
(b2 color red)
(b2 size small)
(b2 supports b1)
(b2 left-of b3)
(b3 shape brick)
(b3 color red)
(b3 size small)
(b3 supports b1)
(b3 right-of b2)
(b4 shape pyramid)
(b4 color blue)
(b4 size large)
(b4 supported-by b5)
(b5 shape cube)
(b5 color green)
(b5 size large)
(b5 supports b4)
(b6 shape brick)
(b6 color purple)
(b6 size large)))
(defun match-element (symbol1 symbol2)
(or (equal symbol1 symbol2)
(equal symbol2 '?)))
(defun match-triple (entry pattern)
(every #'match-element entry pattern))
(defun fetch (pattern)
(remove-if-not #'(lambda (entry)
(match-triple entry pattern))
*database*))
(defun color-pattern (block)
(cons block '(color ?)))
(defun support-pattern (block)
(append '(? supports) (list block)))
(defun supporters (block)
(mapcar #'first (fetch (support-pattern block))))
(defun shape-pattern (block)
(cons block '(shape ?)))
(defun supp-cube (block)
(find-if #'(lambda (blk)
(find-if #'(lambda (entry)
(equal (third entry) 'cube))
(fetch (shape-pattern blk))))
(supporters block)))
(defun description (block)
(reduce #'append (desc2 block)))
(defun desc1 (block)
(remove-if-not #'(lambda (entry)
(equal (first entry) block))
*database*))
(defun desc2 (block)
(mapcar #'(lambda (entry)
(cdr entry))
(desc1 block)))
(defun description (block)
(mapcar #'append (desc2 block)))