所以,我花了很多时间阅读并重新阅读 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))))
当我们将它应用于参数时,返回一个函数
而B
从而产生了自我应用的无限回归。我很难过。如果B受到这个问题的困扰,我不知道A如何避免它。不返回函数
答案 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)
,我们会再次回到我们开始的地方,应用h
在h
。循环。
当然作者都知道这一点。他们希望我们也能理解它。
所以罪魁祸首很简单 - 它是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.
表达式不依赖于h
和g
中的任何一个。所以我们可以把它拿出来,把它作为一个参数,然后留下...... 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”。 然后单击步进按钮(绿色三角形后跟一个条形图)。
这是第一步:
然后为第二个函数做一个例子,看看出了什么问题。