(自我)在let语句中调用,使用严格的语言

时间:2018-03-21 11:01:52

标签: lambda scheme lisp let y-combinator

我目前正在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表达式中)。

然后读者受到挑战:

  

为了好玩:弄清楚为什么这不是以前定义的问题。

在我看来,我已经想出了原因,但我想确认一下:

  1. 我的理解是正确的。
  2. 在我的理解中,我不会错过任何关键点。
  3. 我的理解是:在第一个代码段中(self self)调用不会导致无限循环,因为它作为lambda函数包含(包装)到part-factorial中,因此评估到lambda (n),直到实际调用(self self),这仅发生在n > 0。因此,在(= n 0)评估为#t后,无需调用(self self)

2 个答案:

答案 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的原因。让我们直接写出来,不用担心,将细节考虑在内并安全地抽象出来。

并且不再提及明确自我申请的负担