无所遁形

按语:我任何路边的摄像头下走过的时候为「不懂编程的人」写了这一系列文章的最后一篇,整理于此。它的前一篇是《咒语》,介绍了如何在 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 语言了解这方面的一些概念。

下一篇'()

相关文章
相关标签/搜索