虽说Dan Friedman 大大的《The Little Schemer》确实是通俗易懂,跟一科普读物一样,照顾我们这些小白,但是看到第九章讲Y组合子还是费了一番功夫的,不知道是我笨还是 Y 确实是个不好理解的东西,反正各位看看就行,我就是相当于复习一下功课,让自己能够跟的上思路,写的不好就喷吧,我也不怕,反正菜。
正文开始,说起Y组合子这个东西,我知道的有两种,应用序和正则序,区别就是参数求值的时机不一样
1.应用序是先对参数求值再pass给function
2.正则序则不是这样,正则序又有一个别名,叫lazy evalution,就是说一个表达式能不求值就不去求值它,除非到了要用到它的值了的时候
这篇文章讨论的是应用序,关于正则序,网上文章一大堆,我也是只能理解个大概(笨得吃翔)。不废话了,现有一个累加器:
(define acc
(lambda (n)
(cond ((= n 0) 0)
(else (+ n (acc (- n 1)))))))
现在这个累加器实现了一个线性递归,通过自己调用自己。因为define 把 acc 绑定到了这个lambda表达式上,所以我们才可以调用它自身,但要是没有define这种东西呢?我们又该如何实现一个递归呢? 毕竟lambda calculi 就是没有define这种东西的
</pre><span style="white-space:pre"></span><pre name="code" class="plain">(lambda (n)
(cond ((= n 0) 0)
(else (+ n (? (- n 1))))))
是吧?没有acc我们如何调用自身,让我们修改一下这个lambda,接受一个另外的参数--递归函数
(lambda (acc)
(lambda (n)
(cond ((= n 0) 0)
(else (+ n (acc (- n 1)))))))
如果我们把它自身传给它,是不是就是实现了递归? 我们试试看:
(lambda (acc)
((acc acc))
(lambda (acc)
(lambda (n)
(cond ((= n 0) 0)
(else (+ n (acc (- n 1))))))))
很明显第一个lambda接受一个参数,然后把自身应用到自身,我们看看,这个参数就是第二个lambda 我们 把应用过程画下来:
(lambda (acc)
(lambda (n)
(cond ((= n 0) 0)
(else (+ n (lambda (acc)
(lambda (n)
(cond ((= n 0) 0)
(else (+ n (acc (- n 1))))))
(- n 1)))))))
有个问题不知道你注意到没有,就是累加函数(第二个lambda)接受一个参数,返回另一个函数,所以在 (acc (- n 1))这里就有问题了,它返回了一个函数,并把(- n 1)当作一个函数再次应用,明显不行。所以我们得再想办法。如果我们再给acc喂一个acc呢?
(lambda (acc)
((acc acc))
(lambda (acc)
(lambda (n)
(cond ((= n 0) 0)
(else (+ n ((acc acc) (- n 1))))))))
这样(acc acc)返回的就是一个接受n的函数了,我们刚好传递(- n 1)给它,但是貌似又出问题了,这个函数貌似没有结束的一天。。。因为acc不断应用到acc,不断应用。。囧,不管了,我们先把函数调整一下,acc 是累加器,而我们还得制造累加器,所以:
(lambda (mk-acc)
((mk-acc mk-acc))
(lambda (mk-acc)
(lambda (acc)
(lambda (n)
(cond ((= n 0) 0)
(else (+ n (acc (- n 1)))))))
(mk-acc mk-acc))))
注意我把累加器分离出来了,就是从那个lambda (n)开始的。但问题没变,(mk-acc mk-acc) 返回的是一个接受参数返回一个函数的函数,所以老规矩,再喂一个x给它:
<span style="font-family: Arial, Helvetica, sans-serif;">(lambda (mk-acc)</span>
((mk-acc mk-acc))
(lambda (mk-acc)
(lambda (acc)
(lambda (n)
(cond ((= n 0) 0)
(else (+ n (acc (- n 1)))))))
(lambda (x)
((mk-acc mk-acc) x))))
</pre><pre name="code" class="plain" style="color: rgb(204, 0, 0); line-height: 20.020000457763672px;">这下mk-acc总能返回正确的累加器了吧,好,最后一步,为了得到普遍使用的Y,我们要把累加器彻底分离出来:
(lambda (acc)
((lambda (mk-acc)
(mk-acc mk-acc))
(lambda (mk-acc)
(acc (lambda (x)
((mk-acc mk-acc) x)))))
(lambda (acc)
(cond ((= n 0) 0)
(else (+ 1 (acc (- n 1)))))))
前半部分就是我们的application-order Y conmbinator了:
(define Y
(lambda (f)
(lambda (mf) (mf mf))
(lambda (mf)
(f (lambda (x) ((mf mf) x)))))))
呼,自己知道写的烂,好歹一个字一个字码出来的,看官们将就着看把。