本以为自己已经基本看懂了课程中所讲述的元循环求值器了,但种种事实证明,还欠火候,那再回过头来看看关于基础代换模型以及相关的一些知识吧。
首先,直接先来看一个基础加法过程的实现:
(define (+ a b)
(if (= a 0)
b
(+ (-1+ a) (1+ b))))
那对上面这个过程,应用代换模型,我们会得到:
(+ 3 4)
(if (= 3 0) 4 (+ (-1+ 3) (1+ 4)))
(+ (-1+ 3) (1+ 4))
(+ (-1+ 3) 5) ;基础代换模型需要规定一个逐次代换的方向,这里我们规定为右到左
(+ 2 5)
(if (= 2 0) 5 (+ (-1+ 2) (1+ 5)))
(+ (-1+ 2) (1+ 5))
(+ (-1+ 2) 6)
(+ 1 6)
(if (= 1 0) 6 (+ (-1+ 1) (1+ 6)))
(+ (-1+ 1) (1+ 6))
(+ (-1+ 1) 7)
(+ 0 7)
(if (= 0 0) 7)
接下来我们再看一看另一种实现方法(只改变代码中的一小部分):
(define (+ a b)
(if (= a 0)
0
(1+ (+ (-1+ a) b))))
对上面的这个问题,若我们依然还是计算“3+4”如果我们使用简化了的代换模型,我们会得到如下“形状”的结果:
(+ 3 4)
(1+ (+ 2 4))
(1+ (1+ (+ 1 4)))
(1+ (1+ (1+ (+ 0 4))))
(1+ (1+ (1+ 4)))
(1+ (1+ 5))
(1+ 6)
7
而如果我们采用上上一种方法,使用简化的代换模型,则会得到下一种“形状”的结果:
(+ 3 4)
(+ 2 5)
(+ 1 6)
(+ 0 7)
7
那我们采取的第二种执行加法的过程,便是一种递归过程;而第一种则是一种迭代过程。
从代换出的结果来看,递归相比迭代,有两点问题,第一,虽然总体存储效率和迭代处于同一量级,但我们能看的出,递归版本的程序执行时是一个“大肚子”的执行过程,程序执行到中局时也许需要不可预测的爆炸的存储空间,对于这个问题,整套课程后半段时其实给出了很好的解决方案,这个解决方案的名字叫“记忆化”(这第二节课时,在中间的提问环节,学生就直接跟老师说出了确切的思路,真是MIT啊,太强了);第二个问题,就是如果是迭代版本的代码,程序运行到中间的某个过程节点时,如果中断了,只要有中断那点时记下的参数,程序就还可以继续向后执行出准确的结果,比如3+4这个过程,如果程序运行到1+6时中断了,那我们若把1和6两个参数传回来,就依然还能正常得出结果7,但如果是递归版本的代码,如果程序执行到一半中断了,那么我们的参数可能就由3和4变成了1和4,不能正确得出结果。
那其实在本节“计算过程”的下一节中,我们要开动脑筋,想办法如何根据一种递归的思路,写出迭代版本的代码。(代码并不多,但对我来说,还是充满挑战性的可以说)
反过来,再看看这节课更前面在讲什么(带有自己现在看过元循环求值器的新的理解):
举几个lisp中的基本过程:
3 -> 3 ;当输入为数字时,可以返回3
quote foo -> foo ;当输入为引用类型时,返回引用的内容本身
symbol -> lookup from environment ;如果是符号,去环境中找符号的定义
conditional condition1 procedure1
condition2 procedure2
...
else alternative-procedure ;按照条件判断的结果执行相应的过程
lambda 形参列表 过程本体 ;lambda语句,返回过程本体和对应的形参列表
(过程名 实参一 实参二 ……) ;对所有实参,将过程名所对应的实体apply到所有实参上
……
那事实上,解释器的工作逻辑就如同上述的语句描述,对每一层括号都要执行一次eval,逢“(过程名 实参一 实参二 ……)”这种格式则需执行一次apply,apply是要把过程名eval出的结果用到所有实参eval出的结果上,如此往复进行,一直到最后,基本的像数字,+号等元素也要最后执行一次eval取出它们各自对应的实体,最终算出结果。其中还涉及一个lookup过程,lookup即到一张随着程序运行会发生变化的对过程名、变量名等符号定义其各自实体的表中去找寻不同符号的定义,表的一般呈现如下:((x,1) (+, 皮亚诺公理所定义的加法过程的机器实现)…),那查表的过程就是,我们去检查表中有没有元素的car是我们要找的那个符号,如果有,就把那项返回,我们再直接拿取那一项的cdr即可。
3
eval:
eval 3 -> 3 is number -> 3
'foo
eval:
eval 'foo -> car item of 'foo is quote -> foo
(cond ((= 1 1) 3)) ;习惯上一般还有一个else,这里没写
eval:
eval (cond ((= 1 1) 3))
-> the car item of the expression is cond, so handle it as cond sentence
-> eval ((= 1 1) 3)
-> (= 1 1) is true return 3 ;下面这句很重要,每一项都要求值,所以:
-> eval 3
-> 3
((lambda (x) (+ x 1)) 1)
eval:
eval ((lambda (x) (+ x 1)) 1) ;可以发现外面是要把一个过程用到实参1上,因此要执行apply
(apply eval(lambda (x) (+ x 1))
eval 1)
eval((+ x 1) bind(x, 1)) ;绑定了形参和实参
eval(+ x 1) env: bind(x, 1)
apply (eval+
eval x , eval 1 env: bind(x, 1))
+ lookup x 1
+ 1 1
(很混乱,主要是自己的一个尝试性的练习)