“The Little Schemer”中的Y组合讨论

时间:2012-05-08 13:24:00

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

所以,我花了很多时间阅读并重新阅读 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如何避免它。

2 个答案:

答案 0 :(得分:37)

好问题。为了没有正常运行的DrRacket安装(包括我自己),我会尝试回答它。

首先,让我们使用理智的名字,可以通过人眼/思维轻松追踪:

((lambda (h)     ; A.   
     (h h))            ; apply h on h
 (lambda (g)
     (lambda (lst)
       (if (null? lst) 0
         (add1 
               ((g g) (cdr lst)))))))

第一个lambda术语是所谓的omega 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形式 - 以及与环境框架(即那些允许绑定)之间隐藏的链接。

lambda表达式与其定义环境的这种配对称为closure。对于外部世界,它只是一个参数lst的另一个函数。目前还没有更多的减少步骤来执行。

现在,当关闭 - 我们的list-length函数 - 将被调用时,执行最终将达到(g g)自我应用的程度,并且将再次执行与上述相同的减少步骤。但不是更早。


现在,那本书的作者想要到达Y组合子,所以他们在第一个表达式上应用了一些代码转换,以某种方式安排自动执行(g g) - 所以我们可以以正常方式(f x)编写递归函数应用程序,而不必为所有递归调用将其写为((g g) x)

((lambda (h)     ; B.
     (h h))            ; apply h on 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甚至可以作为闭包返回到运行时系统。我们只希望在执行到达里面 lambda表达式之后,在调用闭包之后将其缩小。在关闭甚至创建之前减少它是荒谬的。 微妙的错误。 :)

当然,由于g绑定到h(g g)会缩减为(h h),我们会再次回到我们开始的地方,应用hh。循环。


当然作者都知道这一点。他们希望我们也能理解它。

所以罪魁祸首很简单 - 它是applicative order of evaluation评估参数之前绑定是由函数的形式参数及其参数的组成的值

代码转换并不完全正确。它将在normal order下工作,其中参数不会提前评估。

这可以通过“eta-expansion”轻松解决,这会将应用程序延迟到实际的呼叫点:(lambda (x) ((g g) x))实际上说:“ 在使用((g g) x)“参数调用时调用x

这实际上是代码转换应该首先出现的内容:

((lambda (h)     ; C.
     (h h))            ; apply h on 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))))
      (lambda (lst)
            (if (null? lst) 0
              (add1 (f (cdr lst))))))))

并且闭包(lambda (lst) ...)形成并且没有问题地返回,并且当调用(f (cdr lst))时(在闭包内)它正如我们想要的那样减少到((g g) (cdr lst))。 / p>


最后,我们注意到(lambda (f) (lambda (lst ...))中的C.表达式不依赖于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事件是一个理论练习。事物,指的是事物。

顺便说一下,指向事物的能力是高等灵长类动物与其他动物王国/生物的区别,或者我听说过。:)< / p>

答案 1 :(得分:23)

要了解会发生什么,请使用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))

然后选择教学语言“中级学生与lambda”。 然后单击步进按钮(绿色三角形后跟一个条形图)。

这是第一步:

enter image description here

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