"The Little Schemer"中的Y组合讨论

pla*_*ian 33 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))))
Run Code Online (Sandbox Code Playgroud)

第170页(第4版)指出A.

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

而B

不返回功能

从而产生自我应用的无限回归.我很难过.如果B受到这个问题的困扰,我不知道A如何避免它.

Wil*_*ess 37

好问题.为了没有运行DrRacket安装(包括自己)的人的利益,我会尝试回答它.

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

((lambda (h)     ; A.   
     (h h))            ; apply h on h
 (lambda (g)
     (lambda (lst)
       (if (null? lst) 0
         (add1 
               ((g g) (cdr lst)))))))
Run Code Online (Sandbox Code Playgroud)

第一个lambda术语是所谓的欧米茄组合.当应用于某些东西时,它会导致该术语的自我应用.因此,上述等同于

(let ((h (lambda (g)
           (lambda (lst)
             (if (null? lst) 0
               (add1 ((g g) (cdr lst))))))))
  (h h))
Run Code Online (Sandbox Code Playgroud)

何时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)))))))
Run Code Online (Sandbox Code Playgroud)

现在没有什么可以应用了,所以lambda返回内部表单 - 以及与环境框架(即那些允许绑定)之间隐藏的链接.

lambda表达式与其定义环境的这种配对称为闭包.对于外界来说,它只是一个参数的另一个功能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)
Run Code Online (Sandbox Code Playgroud)

经过几个减少步骤,我们到达

(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))))
Run Code Online (Sandbox Code Playgroud)

这相当于

(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))))))))
Run Code Online (Sandbox Code Playgroud)

并且遇到了麻烦:自我应用程序(g g)执行得太早,之后内部lambda甚至可以作为闭包返回到运行时系统.我们只希望当执行到了这一点它可以减少内部的lambda表达式,关闭被称为后.在关闭甚至创建之前减少它是荒谬的.一个微妙的错误.:)

当然,由于g必然h,(g g)降低到(h h)和我们又回来了,我们开始的地方,应用hh.循环.


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

因此罪魁祸首很简单 - 它是评估的应用顺序:在绑定由函数的形式参数及其参数组成之前评估参数.

代码转换并不完全正确.它将在正常的顺序下工作,其中参数不会提前评估.

通过" eta-expansion " 可以很容易地解决这个问题," 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)))))
Run Code Online (Sandbox Code Playgroud)

现在可以执行下一个减少步骤:

(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))))))))
Run Code Online (Sandbox Code Playgroud)

并且闭合(lambda (lst) ...)成形并且没有问题地返回,并且当(f (cdr lst))被调用时(在闭合内部)它被缩小到((g g) (cdr lst))我们想要的那样.


最后,我们注意到(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
Run Code Online (Sandbox Code Playgroud)

所以现在,在函数上调用Y等同于从中生成递归定义:

( y (lambda (f) (lambda (x) .... (f x) .... )) ) 
===  define f = (lambda (x) .... (f x) .... )
Run Code Online (Sandbox Code Playgroud)

...但是使用letrec(或命名为let)更好 - 更有效,在自引用环境框架中定义闭包.整个Y事件是系统的理论练习,在那里不可能 - 即不可能命名的东西,创建具有"指向"事物的名称的绑定,指的是事物.

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


soe*_*ard 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))
Run Code Online (Sandbox Code Playgroud)

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

这是第一步:

在此输入图像描述

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