【问题标题】:Y combinator discussion in "The Little Schemer"“The Little Schemer”中的 Y 组合器讨论
【发布时间】:2012-05-16 23:45:29
【问题描述】:

所以,我花了很多时间阅读和重读The Little Schemer 中第 9 章的结尾,其中应用 Y 组合器是为 length 函数开发的。我认为我的困惑归结为一个对比两个版本的长度的声明(在组合子被分解之前):

A:
  ((lambda (mk-length)
     (mk-length mk-length))
   (lambda (mk-length)
     (lambda (l)
       (cond
         ((null? l) 0 )
         (else (add1
                ((mk-length mk-length)
                 (cdr l))))))))

B:
((lambda (mk-length)
      (mk-length mk-length))
    (lambda (mk-length)
      ((lambda (length)
         (lambda (l)
           (cond
             ((null? l) 0)
             (else (add1 (length (cdr l)))))))
       (mk-length mk-length))))

Page 170 (4th ed.) 声明 A

当我们将它应用于参数时返回一个函数

而 B

不返回函数

从而产生自我应用的无限回归。我被这件事难住了。如果B被这个问题困扰,我看不出A是如何避免的。

【问题讨论】:

    标签: scheme combinators y-combinator the-little-schemer


    【解决方案1】:

    要查看会发生什么,请使用 DrRacket 中的步进器。 步进器允许您查看所有中间步骤(以及来回走动)。

    将以下内容粘贴到 DrRacket:

    (((lambda (mk-length)
        (mk-length mk-length))
      (lambda (mk-length)
        (lambda (l)
          (cond
            ((null? l) 0 )
            (else (add1
                   ((mk-length mk-length)
                    (cdr l))))))))
     '(a b c))
    

    然后选择教学语言“Intermediate Student with lambda”。 然后单击步进按钮(绿色三角形后跟一个条形)。

    这是第一步的样子:

    然后为第二个函数做一个例子,看看哪里出了问题。

    【讨论】:

      【解决方案2】:

      很好的问题。为了那些没有正常运行 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 ...)) 表达式不依赖于hg 中的任何一个。所以我们可以把它拿出来,让它成为一个论点,然后剩下...... 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 事情是系统的理论练习,这是不可能的 - 即不可能命名事物,创建绑定 名称​​“指向”事物,指代事物。

      顺便说一句,指向事物的能力是高等灵长类动物与其他动物界⁄生物的区别,至少我听说了。 :)

      【讨论】:

      • 谢谢!! :) 我考虑添加推导 Y 本身的最后一步……这是下一个合乎逻辑的事情。我记得自己对整个 Y 的事情/谜团感到迷惑。不必如此。它经常出现在前机器上。比喻性的描述有很多种,但实际的推导却没有。我喜欢看到理由,然后是推导。 小步骤。 :)
      • 感谢您的解释。我被困在这部分,我感觉上面提到的第一个lambda 表达式也等同于let 形式,但直到读到这里我才完全确定——更不用说它被称为“欧米茄组合器”。这些信息会很有帮助。我想我仍然需要花一些时间来追踪 Y 组合器的输出,但感觉比(在我看来)作者对这个概念相当乏味的解释要模糊得多。
      • 欢迎。 :) "little" 解释与"Little" 书中通常的解释... :) 他们留给读者自己形成解释;我个人不知道这是否是个好主意。否则今天就没有大学和教科书了……我们应该站在巨人的肩膀上,而不是站在他们的脚步上……还有,这是@ 987654327@: "ω := λx.xx ; Ω := ω ω" 和 en.wikipedia.org/wiki/OmegaΩ 称为“欧米茄组合器”,因此这可能并不完全正确。
      • 好的。感谢您的澄清。还有一个问题 - (A) 中的第二个 lambda 也是 little omega-combinator 的一个例子吗? IE。 (lambda (g) (rest-of-function)) 是否类似于第一个 (lambda (h) (h h))。我试图了解调用 (g g) 如何导致第二个 lambda 表达式的递归......
      • little-omega 只是一个通常用来指代(λx.x x) 术语的名称。 A 中的第二个lambda 肯定是别的东西——它是以某种方式编写的递归过程的定义,可以通过自我应用来“快速启动”。由于此(lambda(g) ...) 应用于自身,因此在g 指代整个lambda 术语(lambda(g) ...) 的位置形成变量绑定。之后的调用(g g) 因此是相同的自我应用,具有相同的结果。
      猜你喜欢
      • 2012-09-06
      • 1970-01-01
      • 2014-06-28
      • 2010-12-30
      • 2011-10-23
      • 2011-06-14
      • 2011-11-10
      • 2015-10-22
      • 1970-01-01
      相关资源
      最近更新 更多