博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
无所遁形
阅读量:7237 次
发布时间:2019-06-29

本文共 2438 字,大约阅读时间需要 8 分钟。

按语:我任何路边的摄像头下走过的时候为「不懂编程的人」写了这一系列文章的最后一篇,整理于此。它的前一篇是《》,介绍了如何在 Emacs Lisp 程序的世界里登坛作法,呼风唤雨。

还记得

(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 了,这样可以让代码更清晰一些:

(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)

匿名函数可以作为参数传递给 list-map,那么有名的函数可不可以?试试看:

(defun ++ (x) (+ x 1))(list-map '(1 2 3) ++)

不行。Emacs Lisp 解释器抱怨,++ 是无效的变量。它的抱怨没错,++ 是个函数,不是变量。虽然在逻辑上,变量与函数不用分得太清,但是 Emacs Lisp 解释器从形式上分不清什么是函数,什么是变量。不过,其他 Lisp 方言,例如 Scheme 就能够分辨出来。归根结底,还是 Emacs Lisp 的年代过于久远导致。

在 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 语言了解这方面的一些概念。

下一篇'()

转载地址:http://axgfm.baihongyu.com/

你可能感兴趣的文章
Jsoup
查看>>
python中的中文编码问题
查看>>
安卓播放音频
查看>>
in linux system of ftp command
查看>>
Win API:之GetCurrentThread、GetCurrentThreadId、GetCurrentProcess、GetCurrentProcessId
查看>>
***PHP $_FILES函数详解 + PHP文件上传 move_uploaded_file() 参数的正确写法
查看>>
Mysql中Group By使用Having语句配合查询(where和having区别)
查看>>
C#连接数据库
查看>>
重定向和管道的区别
查看>>
分层、链式分析、url、联系的长度
查看>>
C++实现ping功能<转>
查看>>
使用matplotlib绘制收入增长模型——线性积累型与指数复利型
查看>>
【Spark】Spark-Redis连接池
查看>>
网络流简介
查看>>
How to fix “HTTP Status Code 505 – HTTP Version Not Supported” error?--转
查看>>
mybatis结合mysql批量操作及查询sql
查看>>
groovy gradle 构建配置
查看>>
Linux时间子系统(十五) clocksource
查看>>
BaseRecyclerViewAdapterHelper使用
查看>>
请说出三种减少页面加载时间的方法。
查看>>