很好的问题。为了那些没有正常运行 DrRacket 安装的人的利益(包括我自己)我会尝试回答它。
首先,让我们使用一些健全的(简短的)变量名称,人眼/头脑可以轻松跟踪:
((lambda (h) ; A.
(h h)) ; apply h to h
(lambda (g)
(lambda (lst)
(if (null? lst) 0
(add1
((g g) (cdr lst)))))))
第一个 lambda 项就是所谓的little omega, or U combinator。当应用于某物时,它会导致该术语的自我应用。因此以上等价于
(let ((h (lambda (g)
(lambda (lst)
(if (null? lst) 0
(add1 ((g g) (cdr lst))))))))
(h h))
当h应用于h时,新的绑定就形成了:
(let ((h (lambda (g)
(lambda (lst)
(if (null? lst) 0
(add1 ((g g) (cdr lst))))))))
(let ((g h))
(lambda (lst)
(if (null? lst) 0
(add1 ((g g) (cdr lst)))))))
现在没有什么可以应用了,所以内部的lambda 表单被返回了——连同它上面的环境框架的隐藏链接(即那些let 绑定)。
这种 lambda 表达式与其定义环境的配对称为closure。对于外界来说,这只是一个参数lst 的另一个功能。目前没有更多的减少步骤可以在那里执行。
现在,当那个闭包——我们的list-length 函数——将被调用时,执行将最终到达(g g) self-application 的点,并且将再次执行上述相同的缩减步骤。但不是更早。
现在,那本书的作者想要得到 Y 组合子,因此他们对第一个表达式应用了一些代码转换,以某种方式安排执行该自应用 (g g)自动 - 所以我们可以以正常的方式编写递归函数应用程序,(f x),而不必为所有递归调用编写为 ((g g) x):
((lambda (h) ; B.
(h h)) ; apply h to h
(lambda (g)
((lambda (f) ; 'f' to become bound to '(g g)',
(lambda (lst)
(if (null? lst) 0
(add1 (f (cdr lst)))))) ; here: (f x) instead of ((g g) x)!
(g g)))) ; (this is not quite right)
现在经过几个简化步骤,我们到达了
(let ((h (lambda (g)
((lambda (f)
(lambda (lst)
(if (null? lst) 0
(add1 (f (cdr lst))))))
(g g)))))
(let ((g h))
((lambda (f)
(lambda (lst)
(if (null? lst) 0
(add1 (f (cdr lst))))))
(g g))))
相当于
(let ((h (lambda (g)
((lambda (f)
(lambda (lst)
(if (null? lst) 0
(add1 (f (cdr lst))))))
(g g)))))
(let ((g h))
(let ((f (g g))) ; problem! (under applicative-order evaluation)
(lambda (lst)
(if (null? lst) 0
(add1 (f (cdr lst))))))))
麻烦来了:(g g) 的自应用执行得太早了,在内部 lambda 甚至可以作为闭包返回给运行时系统。我们只希望在执行到 inside lambda 表达式的那个点时减少它,在调用闭包之后。在创建闭包之前减少它是荒谬的。 微妙的错误。 :)
当然,由于g 绑定到h,(g g) 被简化为(h h),我们又回到了我们开始的地方,将h 应用于h。循环播放。
作者当然知道这一点。他们希望我们也能理解它。
所以罪魁祸首很简单——它是applicative order of evaluation:评估参数之前绑定是由函数的形参及其参数的值形成的.
那么,代码转换不太正确。它本来可以在 normal order 下工作,其中没有预先评估参数。
这很容易通过“eta-expansion”来解决,它将应用程序延迟到实际调用点:(lambda (x) ((g g) x)) 实际上说:“将调用@987654353 @ 当使用 x" 的参数调用时。
这实际上是代码转换应该放在首位的:
((lambda (h) ; C.
(h h)) ; apply h to h
(lambda (g)
((lambda (f) ; 'f' to become bound to '(lambda (x) ((g g) x))',
(lambda (lst)
(if (null? lst) 0
(add1 (f (cdr lst)))))) ; here: (f x) instead of ((g g) x)
(lambda (x) ((g g) x)))))
现在可以执行下一个缩减步骤:
(let ((h (lambda (g)
((lambda (f)
(lambda (lst)
(if (null? lst) 0
(add1 (f (cdr lst))))))
(lambda (x) ((g g) x))))))
(let ((g h))
(let ((f (lambda (x) ((g g) x)))) ; here it's OK
(lambda (lst)
(if (null? lst) 0
(add1 (f (cdr lst))))))))
并且闭包(lambda (lst) ...) 形成并返回没有问题,当(f (cdr lst)) 被调用(在闭包内)时,它会减少为((g g) (cdr lst)),就像我们想要的那样。
最后,我们注意到C. 中的(lambda (f) (lambda (lst ...)) 表达式不依赖于h 和g 中的任何一个。所以我们可以把它拿出来,让它成为一个论点,然后剩下...... Y 组合子:
( ( (lambda (rec) ; D.
( (lambda (h) (h h))
(lambda (g)
(rec (lambda (x) ((g g) x)))))) ; applicative-order Y combinator
(lambda (f)
(lambda (lst)
(if (null? lst) 0
(add1 (f (cdr lst)))))) )
(list 1 2 3) ) ; ==> 3
所以现在,在一个函数上调用 Y 就相当于用它进行递归定义:
( y (lambda (f) (lambda (x) .... (f x) .... )) )
=== define f = (lambda (x) .... (f x) .... )
...但是使用letrec(或命名为let)更好——更高效,defining the closure in self-referential environment frame。整个 Y 事情是系统的理论练习,这是不可能的 - 即不可能命名事物,创建绑定 名称“指向”事物,指代事物。
顺便说一句,指向事物的能力是高等灵长类动物与其他动物界⁄生物的区别,至少我听说了。 :)