我目前正在this article通过Y-combinator进行Mike Vanier。
沿着Y-combinator推导的方式,这段代码:
(define (part-factorial self)
(lambda (n)
(if (= n 0)
1
(* n ((self self) (- n 1))))))
((part-factorial part-factorial) 5) ==> 120
(define factorial (part-factorial part-factorial))
(factorial 5) ==> 120
的目的是:
(define (part-factorial self)
(let ((f (self self)))
(lambda (n)
(if (= n 0)
1
(* n (f (- n 1)))))))
(define factorial (part-factorial part-factorial))
(factorial 5) ==> 120
之后,文章指出:
这将在懒惰的语言中正常工作。在严格的语言中,let语句中的
(self self)
调用会将我们发送到无限循环,因为为了计算(part-factorial part-factorial)
(在阶乘的定义中),您首先必须计算(部分 - factorial part-factorial)(在let
表达式中)。
然后读者受到挑战:
为了好玩:弄清楚为什么这不是以前定义的问题。
在我看来,我已经想出了原因,但我想确认一下:
我的理解是:在第一个代码段中(self self)
调用不会导致无限循环,因为它作为lambda
函数包含(包装)到part-factorial
中,因此评估到lambda (n)
,直到实际调用(self self)
,这仅发生在n > 0
。因此,在(= n 0)
评估为#t
后,无需调用(self self)
。
答案 0 :(得分:3)
是的,这是正确的答案。事实上,在为应用程序语言定义Y时,这个技巧(包装一些本来可以在lambda中递归的东西)是至关重要的,我认为他的文章谈到了(顺便说一句它是一篇好文章)。
答案 1 :(得分:3)
是的,第二个定义中的“let-over-lambda”
(define (part-factorial self)
(let ((f (self self))) ; let above the
(lambda (n) ; lambda
(if (= n 0)
1
(* n (f (- n 1)))))))
导致在返回(self self)
之前触发应用程序(lambda (n) ...)
。
这表明避免循环的另一种方法:将有问题的自我应用程序本身放在自己的 lambda
之后:
(define (part-factorial self)
(let ((f (lambda (a) ((self self) a))))
(lambda (n)
(if (= n 0)
1
(* n (f (- n 1)))))))
现在这也有效。它被称为“eta-expansion”(或“eta-conversion”,因为它的对偶是“eta-contraction”)。
这种方式实际上是在通常的“应用顺序Y-combinator”定义中使用的方式。
在第一个定义中,(self self)
应用程序仅在实际需要其结果时触发 - 但是它的成本是我们必须以某种“不自然”的方式编写它,这在任何情况下都是不同的从我们想要编写的内容开始,即只使用f
来引用函数,该函数以某种方式为我们在幕后进行递归。
通过明确的自我应用,负担在我们身上,并且我们知道人类犯错误。毕竟,犯错是人,原谅 - 神圣;但是我们的电脑还没有完全放弃的。。
所以,这个就是Y的原因。让我们直接写出来,不用担心,将细节考虑在内并安全地抽象出来。
并且不再提及明确自我申请的负担。