博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
长长的望远镜
阅读量:7126 次
发布时间:2019-06-28

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

按语:我在送孩子去幼儿园的路上为「不懂编程的人」写了这一系列文章的第九篇,整理于此。它的前一篇是《》,讲述了如何一步一步「推演」出 Y 组合子。

我有个长长的望远镜,能一直伸到你的家里面,你说什么做什么,我都能看到。

怎样用 Emacs Lisp 语言描述这样的事?

(defun bar () x)(defun foo (x) (bar))(foo '三原色)

(foo '三原色) 进行求值,会在 Emacs 微缓冲区显现 三原色

foo 函数会对 bar 函数进行求值。bar 函数不接受任何参数的函数,但是它的内部却凭空出现了一个变量 x。令 bar 毛骨悚然的是,当 foo 对它求值时,这个 x 竟然有意义的,它的值是符号原子 三原色

foo 的内部,bar 函数觉得自己见了鬼。

这其实是 Emacs Lisp 的动态域(Dynamic domain)在搞鬼。Emacs Lisp 解释器对 (foo '三原色) 求值,得到表达式 (bar),然后它继续对 (bar) 求值,得到表达式 x,最后它继续对 x 求值,结果发现这个 x 是个长长的望远镜,从 foo 的窗户一直伸到了 bar 的家里,这个望远镜的品牌叫 三原色。因此,我们就在微缓冲区看到了匪夷所思的结果。

当 Emacs Lisp 对一个函数表达式求值时,遇到自由变量时,它就会到一个全局的环境中搜索这个自由变量的值,将这个值作为自由变量的求值结果,倘若找不到,就会报错,说变量无效。

动态域的这种特性,对于需要长长的望远镜的机构很有用。不过,对于 bar 函数而言,既然 foo 能把长长的望远镜伸过来,就不要放过它:

(defun bar () (setq x '黑暗))(defun foo (x) (progn (bar) x))(foo '三原色)

再次对 (foo '三原色) 进行求值,这次结果为 黑暗。因为 bar 抓住了这个伸到自己家里的望远镜,把它的镜头涂黑了。

动态域,是很古老的变量作用域模型。现代的变量作用域叫词法域,也叫静态域。

在词法域里,每个函数都有自己的环境。当函数中出现自由变量时,它就在自己的环境里搜索变量的值,搜到了就作为自由变量的求值结果,否则就报错——变量无效。

词法域的好处是,没有人能够将长长的望远镜伸到你家里。看下面的例子:

(setq lexical-binding t)(defun bar () x)(defun foo (x) (bar))(foo '三原色)

再对 (foo '三原色) 求值,就会在微缓冲区报错,说变量 x 无效。这是因为,在 bar 的环境中,x 是未定义的自由变量,所以无效。虽然 foo 中的 x 是有定义的,但它仅仅是与 bar 中的 x 同名而已,它们是两个不同的变量。

下面的代码是一个匿名函数的求值表达式:

(funcall (funcall (lambda (thing)              (lambda (n)                (if (= n 0)                    0                  (+ n (funcall (funcall thing thing) (- n 1))))))            (lambda (thing)              (lambda (n)                (if (= n 0)                    0                  (+ n (funcall (funcall thing thing) (- n 1))))))) 100)

在动态域里,这个函数表达式无法求值,因为当 Emacs Lisp 解释器在对这个表达式进行求值时,最终抵达 (funcall thing thing) 的时候,全局环境里面已经没有了 thing 的定义。因为,当一个函数的求值结果是匿名函数时,在这个匿名函数被求值时,全局环境已经不再是它还在母体时的那个样子了。

例如,Emacs Lisp 解释器对

(funcall (lambda (thing)       (lambda (n)         (if (= n 0)         0           (+ n (funcall (funcall thing thing) (- n 1))))))     (lambda (thing)       (lambda (n)         (if (= n 0)         0           (+ n (funcall (funcall thing thing) (- n 1)))))))

的求值结果是

(lambda (n)  (if (= n 0)      0    (+ n (funcall (funcall thing thing) (- n 1))))))

这时,这个匿名函数里的 thing,在这个匿名函数的母体中是有定义的,它就是作为参数传入的那个匿名函数,但是 Emacs Lisp 对母体求值结束后,thing 的定义也就同时在全局环境中消失了,因此对于这个刚刚从母体中脱胎而出的匿名函数,thing 变成了一个未定义的自由变量,从而导致 Emacs Lisp 解释器报错。

简而言之,在动态域中,你没有办法将匿名函数作为参数传给自身。因此,在动态域中,你看不到世界的本原,这样的世界是一个不确定的世界。这可能就是为什么早期的 Lisp 机器在运行时经常出故障的主要原因。

在词法域里不会有这样的问题,因为每个函数都有自己的环境,并且一个匿名函数从母体脱胎而出的时候,它会对母体的环境有所继承。这种结构称为闭包。

在使用 Emacs Lisp 编程时,用动态域还是词法域呢?倘若你没有长长的望远镜,或者你对这种望远镜深恶痛绝,就用词法域吧,在程序的开头添加:

(setq lexical-binding t)

;;; -*- lexical-binding: t -*-

最后,略微介绍一下 setq。之前,我们只见识过通过函数参数传递的变量。setq 可以将一个符号与一个值或一个匿名函数绑定起来。上面已经见识了它将 lexical-bindingt 绑定了起来。下面的例子展示了如何将符号与匿名函数绑定起来:

(setq Y      (lambda (F)        (funcall (lambda (thing)                   (funcall F                            (lambda (m) (funcall (funcall thing thing) m))))                 (lambda (thing)                   (funcall F                            (lambda (m) (funcall (funcall thing thing) m)))))))(setq F      (lambda (thing*)        (lambda (n)          (if (= n 0)              0            (+ n (funcall thing* (- n 1)))))))

这样绑定之后,用 Y 组合子构造匿名的递归函数会更加简洁:

(funcall (funcall Y F) 100)

要试验上述代码,记得开启词法域模式。

下一篇

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

你可能感兴趣的文章
网络安全实验室_上传关writeup
查看>>
find 命令详解
查看>>
OO第二阶段总结
查看>>
eclipse创建的maven项目中使用javafx
查看>>
如何快速熟悉公司的旧代码
查看>>
python+ddt+unittest+excel+request实现接口自动化
查看>>
第八周周记
查看>>
Jni Error(app bug): accessed stale local reference 的另类出现方式
查看>>
myeclipse 解决乱码问题
查看>>
iOS Https 配置 及AFN 相关配置
查看>>
Table-Valued Parameters in SQL Server 2008 (ADO.NET)
查看>>
推荐系统常用的算法参考
查看>>
[Todo] Java并发编程学习
查看>>
Redis cluster学习 & Redis常识 & sort操作
查看>>
mysql 中实现多条数据同时更新
查看>>
2011 ACM/ICPC 成都赛区(为2013/10/20成都现场赛Fighting)
查看>>
Linux技术进阶示意图
查看>>
php设计模式课程---6、策略模式如何使用
查看>>
html5--6-8 CSS选择器5
查看>>
20145328 《信息安全系统设计基础》第6周学习总结
查看>>