Lambda (self-self)用严格的语言在let语句中调用

Lambda (self-self)用严格的语言在let语句中调用,lambda,scheme,lisp,let,y-combinator,Lambda,Scheme,Lisp,Let,Y Combinator,我现在正在进行Y-combinator的学习。 在Y-组合子推导过程中,此代码: (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)) (fac

我现在正在进行Y-combinator的学习。
在Y-组合子推导过程中,此代码:

(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)
调用将把我们送入一个无限循环,因为为了计算
(部分阶乘部分阶乘)
(在阶乘的定义中),您首先必须计算(部分阶乘部分阶乘)(在
let
表达式中)

然后读者受到挑战:

有趣的是:找出为什么之前的定义没有问题

在我看来,我已经找到了原因,尽管我想确认:

  • 我的理解是正确的
  • 据我所知,我没有遗漏任何关键点

  • 我的理解是:在第一个代码片段中,
    (self-self)
    调用不会导致无限循环,因为它作为一个
    部分阶乘
    函数包含(包装)在
    lambda
    中,并因此求值为
    lambda(n)
    ,直到实际进行了对
    (self-self)
    的调用,这只在
    n>0
    时发生。因此,在
    (=n0)
    计算为
    #t
    后,没有必要调用
    (self-self)
    是的,这是正确的答案。事实上,在为应用程序顺序语言定义Y时,这个技巧(包装可能在lambda中递归的内容)是至关重要的,我认为他的文章谈到了这一点(顺便说一下,这是一篇好文章)。

    是的,这是正确的答案。事实上,在为应用程序顺序语言定义Y时,这个技巧(包装一些本来会在lambda中递归的东西)是至关重要的,我认为他的文章谈到了这一点(顺便说一下,这是一篇好文章)。

    是的,第二个定义中的“忽略lambda”

    (define (part-factorial self)
      (let ((f (self self)))        ; let above the
        (lambda (n)                    ; lambda
          (if (= n 0)
            1
            (* n (f (- n 1)))))))
    
    导致在返回
    (lambda(n)…
    之前触发应用程序
    (self-self)

    这建议了另一种避免循环的方法:将有问题的自我应用程序本身放在自己的
    lambda
    后面:

    (define (part-factorial self)
      (let ((f (lambda (a) ((self self) a))))
        (lambda (n)
          (if (= n 0)
            1
            (* n (f (- n 1)))))))
    
    现在这也起作用了。它被称为“eta扩展”(或者通常称为“eta转换”,因为它的对偶是“eta收缩”)

    这就是通常的“应用顺序Y组合子”定义中实际使用的方法

    在第一个定义中,
    (self-self)
    应用程序只有在实际需要其结果时才会触发-但它的代价是我们必须以某种“不自然”的风格编写它,这在任何情况下都不同于我们想要编写的风格,也就是说,只需使用
    f
    来指代我们在幕后以某种方式进行递归的函数

    有了明确的自我应用,负担就在我们身上,而我们人类也会犯错。毕竟,犯错是人类的行为,就像宽恕是神圣的;但我们的电脑还没有完全处于宽容的阶段

    所以,这就是为什么Y。让我们写得直截了当,不必担心,把细节考虑清楚,安全地抽象出来

    不应再提及明确自我应用的负担。

    是的,第二个定义中的“让渡lambda”

    (define (part-factorial self)
      (let ((f (self self)))        ; let above the
        (lambda (n)                    ; lambda
          (if (= n 0)
            1
            (* n (f (- n 1)))))))
    
    导致在返回
    (lambda(n)…
    之前触发应用程序
    (self-self)

    这建议了另一种避免循环的方法:将有问题的自我应用程序本身放在自己的
    lambda
    后面:

    (define (part-factorial self)
      (let ((f (lambda (a) ((self self) a))))
        (lambda (n)
          (if (= n 0)
            1
            (* n (f (- n 1)))))))
    
    现在这也起作用了。它被称为“eta扩展”(或者通常称为“eta转换”,因为它的对偶是“eta收缩”)

    这就是通常的“应用顺序Y组合子”定义中实际使用的方法

    在第一个定义中,
    (self-self)
    应用程序只有在实际需要其结果时才会触发-但它的代价是我们必须以某种“不自然”的风格编写它,这在任何情况下都不同于我们想要编写的风格,也就是说,只需使用
    f
    来指代我们在幕后以某种方式进行递归的函数

    有了明确的自我应用,负担就在我们身上,而我们人类也会犯错。毕竟,犯错是人类的行为,就像宽恕是神圣的;但我们的电脑还没有完全处于宽容的阶段

    所以,这就是为什么Y。让我们写得直截了当,不必担心,把细节考虑清楚,安全地抽象出来


    而明确的自我应用的负担将不再被提及。

    heavens@naomik你的意思是……)@naomik但是说真的,Y是非常简单的(好吧,至少是可以理解的),如果用适当的动机例子逐步呈现的话。真的让我很生气,因为无数的陈述都是关于它的,完全不可理解的,出乎意料的,没有任何解释。人们为什么这样做?还是因为我运气不好?:)/大智慧heavens@naomik你的意思是……)@naomik但是说真的,Y是非常简单的(好吧,至少是可以理解的),如果用适当的动机例子逐步呈现的话。真的让我很生气,因为无数的陈述都是关于它的,完全不可理解的,出乎意料的,没有任何解释。人们为什么这样做?还是因为我运气不好?:)/排气