按语:我任何路边的摄像头下走过的时候为「不懂编程的人」写了这一系列文章的最后一篇,整理于此。它的前一篇是《咒语》,介绍了如何在 Emacs Lisp 程序的世界里登坛做法,呼风唤雨。编程
还记得segmentfault
(defun list-map (a f) (funcall (lambda (x) (if (null x) nil (cons (funcall f x) (list-map (cdr a) f)))) (car a)))
么?函数
当时,为了表示把手绑起来也能用脚写字,因此故意没用 let
,如今能够坦然地用 let
了,这样可让代码更清晰一些:code
(defun list-map (a f) (let ((x (car a))) (if (null x) nil (cons (funcall f x) (list-map (cdr a) f)))))
这个函数能够将函数 f
做用于 列表 a
中的每一个元素,结果为一个列表。例如:递归
(list-map '(1 2 3) (lambda (x) (+ x 1)))
结果为 (2 3 4)
get
匿名函数能够做为参数传递给 list-map
,那么有名的函数可不能够?试试看:io
(defun ++ (x) (+ x 1)) (list-map '(1 2 3) ++)
不行。Emacs Lisp 解释器抱怨,++
是无效的变量。它的抱怨没错,++
是个函数,不是变量。虽然在逻辑上,变量与函数不用分得太清,可是 Emacs Lisp 解释器从形式上分不清什么是函数,什么是变量。不过,其余 Lisp 方言,例如 Scheme 就可以分辨出来。归根结底,仍是 Emacs Lisp 的年代过于久远致使。function
在 Emacs Lisp 里,须要将上述的 list-map
表达式改为下面这样:匿名函数
(list-map '(1 2 3) (function ++))
或者简写形式:变量
(list-map '(1 2 3) #'++)
用 function
或 #'
告诉 Emacs Lisp 解释器,后面这个符号是函数。这样 Emacs Lisp 就能够正确识别 ++
了。
以上,只是本文的前奏。下面咱们来思考一个更深入的问题。这个问题可能深到无止境的程度。
如今,假设 list-map
所接受的列表是一个嵌套的列表——列表中有些元素也是列表:
(list-map '(1 2 3 (4 5 6) 7 8 9) #'++)
对这个表达式进行求值,发现 list-map
失灵了,++
无法做用于列表元素 (4 5 6)
。++
只能对一个数进行增 1 运算,却不能对一个列表这样作。假若咱们真的很想让 ++
可以继续进入 (4 5 6)
内部,将其中每个元素都增 1,而后再跳出来继续处理 (4 5 6)
后面的元素,该怎么办?
首先,咱们须要具备判断列表中的一个元素是否是列表的能力。Emacs Lisp 提供的 listp
函数可让咱们具备这种能力。例如:
(listp 3) (listp '[1 2 3]) (listp '(1 2 3)) (listp '()) (listp nil)
上面这五个表达式,前两个的求值结果皆为 nil
,后面三个的求值结果皆为 t
。
有了 listp
,咱们就能够区分一个列表元素是原子仍是列表了。能区分,就好办。假若列表元素依然是列表,那么咱们就继续将 list-map
做用于这个元素,而假若它不是列表,那么就用 ++
之类的函数伺候之。
试试看:
(defun list-map (a f) (let ((x (car a))) (if (null x) nil (if (listp x) (cons (list-map x f) (list-map (cdr a) f)) (cons (funcall f x) (list-map (cdr a) f))))))
试验一下这个新的 list-map
能不能用:
(list-map '(1 2 3 (4 5 6) 7 8 9) #'++)
结果获得 (2 3 4 (5 6 7) 8 9 10)
,正确。
再拿更多层数的列表试试看:
(list-map '(1 2 3 (4 5 (0 1)) 7 8 9 (3 3 3)) #'++)
结果获得 (2 3 4 (5 6 (1 2)) 8 9 10 (4 4 4))
,正确。
就这样,咱们只是对 list-map
略动手脚,彷佛就可让不管嵌套有多少层,藏匿有多深的列表,在 list-map
面前都是尽收眼底的。
在未对列表结构有任何破坏的状况下,能够肯定上述的感受是正确的。由于计算机的运转老是周而复始。假若程序自己只变更了数据的形状,而未破坏它的拓扑结构,咱们就老是可以作到见微而知著。
上面对 list-map
的修改,虽然只考虑了再次使用 list-map
来处理列表元素为列表的状况,结果却让 list-map
可以适用于任何形式的列表嵌套。咱们在用宏的形式定义 my-let*
的时候也遇到过这样的状况。为何会这样?这实际上是在周而复始的运动中,出现了类型。listp
可以判断一个值是不是列表类型。在一个 Emacs Lisp 程序里,能够有无数个列表,但它们的类型倒是相同的,都是列表类型。
天网恢恢,疏而不漏,靠的不过是递归 + 类型。类型,描述了值的共性。它生活在柏拉图的理想国里,是一种完美的模具,而那些值只不过是从模具里铸出来的东西。类型是比递归一个更大的题目,已经有许多人写了这方面的专著。假若你对这个感兴趣,能够经过 Haskell 语言了解这方面的一些概念。
下一篇:
'()