Y组合子

在这个文件里我们来生成Y组合子,它是递归过程理论的一个基本结果。可能你已经知道,在某些情况下,是不需要给一个过程命名的。比如说: 
  ((lambda (x) (+ x 1)) 6)
在没有名字的情况下,把1加到6上面去。但是,如果那是递归函数又该如何?比如说: 
  (define fact
    (lambda (n)
      (if (zero? n)
          1
          (* n (fact (- n 1))))))
它计算n的阶乘,看起来它需要名字fact,这样它才能够在过程的最后一行调用自己实现递归。但是,我们将会发现,其实它是不需要的,并且,在发现过程中还会养成许多使用Scheme的直觉。让我们一步一步来,每一步都轻微地改变一下"fact"。 


第一步 第一个想法是像我们经常做的那样,简单地把fact当成一个参数传递进去。
  (define op-maker
    (lambda (op)
      (lambda (x y)
        (op x y))))
第一个lambda传递了操作的名字而第二个lambda是一个未命名的的操作。让我们用"fact"来试一下它。第一个尝试是 
  (define fact-maker
    (lambda (procedure)
      (lambda (n)
        (if (zero? n)
            1
            (* n (procedure (- n 1)))))))
它的思想是通过"procedure"把"fact-maker"传递进去。因此,我们所要做的就是调用(fact-maker fact-maker)来产生我们的未命名(呃……应该是几乎未命名的)的阶乘函数。比如,这样写: 
  >((fact-maker fact-maker) 5)
  120
但是,这不能运行,因为"fact-maker"接收一个过程作为参数,而"procedure",跟"fact"一样,需要一个数值型参数。解决办法是这样的: 
  (define fact-maker
    (lambda (procedure)
      (lambda (n)
         (if (zero? n)
             1
             (* n ((procedure procedure) (- n 1)))))))
试一下它,比如说这样
 >((fact-maker fact-maker) 5)
好了,我们把名字从过程体中除去了,但是我们仍然要通过名字传递一个过程进去。让我们试着把全部对名字的依赖除掉。 


第二步 回想起我们需要"fact"让和(procedure procedure)一样,也就是和(fact-maker fact-maker)一样(回想上面的例子,((fact-maker fact-maker) 5)得出和(fact 5)一样的结果)。因此,利用第一步得到的结果,我们可以把"fact-maker"写成下面的样子: 
  (define fact
    ((lambda (procedure)
       (lambda (n)
         (if (zero? n)
             1
             (* n ((procedure procedure) (- n 1))))))
     (lambda (procedure)
       (lambda (n)
         (if (zero? n)
             1
             (* n ((procedure procedure) (- n 1))))))))
用>(fact 5) 试一下它。 
考虑下面的代码段: 
  (((lambda (procedure)
      (lambda (n)
        (if (zero? n)
            1
            (* n ((procedure procedure) (- n 1))))))
    (lambda (procedure)
      (lambda (n)
        (if (zero? n)
            1
            (* n ((procedure procedure) (- n 1)))))))
   5)
它产生了5的阶乘,因为被调用的过程(那一大堆代码)正好是"fact"的定义。但是,你瞧,过程里面已经没有任何名字了! 
接下来,我们尝试把这个过程一般化,来结束我们那可怕但有用的Y组合子。 


第三步 首先,我们要把属于计算阶乘的那一部分区别开来。目的是把这部分写在一个地方,当用其它问题的代码替代掉它以后,会生成一个新的递归过程。这一步有些技巧,我们依然给过程一个名字,因为我们不想作大的改动。我们从第二步得到的代码段是 
  (define F
    (lambda (n)
      (if (zero? n)
          1
          (* n ((procedure procedure) (- n 1))))))
因为它包含一个看得见的平凡的过程(procedure procedure),它跟我们想要的还有差距。所以我们用了一个小技巧来把它除去。一般地, 
  (f arg)
不就跟 
  ((lambda (x) (f x)) arg) 一样吗?
尽管第二个语句有点奇怪,但是因为它让你把"arg"传递给一个要被应用到的过程,无论如何,过程被应用了。为什么我们要这样做?看!这意味着 
  ((procedure procedure) (- n 1))
跟 
  ((lambda (arg) ((procedure procedure) arg)) (- n 1))是一样的。
如果我们把这个替换到当前版本的F中,得到 
  (define F
    (lambda (n)
      (if (zero? n)
          1
          (* n ((lambda (arg) ((procedure procedure) arg)) (- n 1))))))
这有什么用?呃,(lambda (arg)...)是一个过程, 而procedure可以当成一个参数传递进去,因此,F可以定义成这样: 
  (define F
    ((lambda (func-arg)
       (lambda (n)
         (if (zero? n)
             1
             (* n (func-arg (- n 1))))))
     (lambda (arg) ((procedure procedure) arg))))
没错,它就是那个F,但是旧的定义看起来像这样: 
  (define F (lambda (n) ... < procedure >))
而这新定义看起来像这样: 
  (define F ((lambda (func-arg) (lambda (n) ...)) < procedure >))
上式中,< procedure > 就是 (lambda (arg) ((procedure... ) ...) ...) 表达式。 

 

作者:林杰杰      发表时间:2006-6-23 17:31:00

 1楼  

第四步 现在我们准备好把第三步的结果拿出来并把它应用到第二步的结果去。把整个过程写出来,我们得到: 
  (define fact
    ((lambda (procedure)
       ((lambda (func-arg)
          (lambda (n)
            (if (zero? n)
                1
                (* n (func-arg (- n 1))))))
        (lambda (arg) ((procedure procedure) arg))))
     (lambda (procedure)
       ((lambda (func-arg)
          (lambda (n)
            (if (zero? n)
                1
                (* n (func-arg (- n 1))))))
        (lambda (arg) ((procedure procedure) arg))))))
你可很要认真地研究一下这个。注意((lambda (func-arg)... 前面有两个左括号,这是因为我们这样写 
   ...
   ((lambda (func-arg) < body-using-func-arg >) (lambda (arg) ...))
跟 
  ((lambda (arg) ((procedure procedure) arg)) (- n 1))
形式一样,但是不同之处在于,过程代替了整数来作参数传递。 
以(lambda (func-arg) ...)开头的两个表达式刚好与计算阶乘的代码对应,并且是正确的形式。所以,我们可以这样把它们除出去: 
  (define F*
    (lambda (func-arg)
      (lambda (n)
        (if (zero? n)
            1
            (* n (func-arg (- n 1)))))))
  (define fact
    ((lambda (procedure)
       (F* (lambda (arg) ((procedure procedure) arg))))
     (lambda (procedure)
       (F* (lambda (arg) ((procedure procedure) arg))))))
这是很重要的,因为我们可以通过用任何过程代替F*来把函数功能改变成我们想要的。唯一的问题就是,就像写出来的一样,我们还是需要为F*命名。在下一步中这很容易补救。 


第五步 赌一把!现在我们来写可怕的Y组合子: 
  (define Y
    (lambda (X)
      ((lambda (procedure)
         (X (lambda (arg) ((procedure procedure) arg))))
       (lambda (procedure)
         (X (lambda (arg) ((procedure procedure) arg)))))))
注意,现在执行我们的计算的过程是X(我们不再使用F*,以示这代码可以应用到任何过程中去),是以参数形式传递进去的。 


第六步 用Y组合子的方法,我们可以这样写"fact": 
  (define fact (Y F*))
用>(fact 5)来检查一下结果, 同时尝试>((Y F*) 5)。但是Y是一般化的,而F*是计算阶乘而没有名字的!如果我们把所有的东西都写出来,它应该是这样的: 
  (((lambda (X)
      ((lambda (procedure)
         (X (lambda (arg) ((procedure procedure) arg))))
       (lambda (procedure)
         (X (lambda (arg) ((procedure procedure) arg))))))
    (lambda (func-arg)
      (lambda (n)
        (if (zero? n)
            1
            (* n (func-arg (- n 1)))))))
   5)
看吧!没有名字!为了展示它的一般性,让我们用Y组合子来写其它的过程。比如说findmax——找出列表中最大的元素。 
  (define findmax
    (lambda (l)
      (if (null? l)
          'no-list
          (if (null? (cdr l))
              (car l)
              (max (car l) (findmax (cdr l)))))))
首先,写出对应于fact中的F*的类似物,把它叫做M,表示max 
  (define M
    (lambda (func-arg)
      (lambda (l)
        (if (null? l)
            'no-list
            (if (null? (cdr l))
                (car l)
                (max (car l) (func-arg (cdr l))))))))
现在试一下((Y M) '(4 5 6 3 4 8 6 2))看看它能不能运行。如果你要扩展它,那它看起来应该是这样的: 
  (((lambda (X)
      ((lambda (procedure)
         (X (lambda (arg) ((procedure procedure) arg))))
       (lambda (procedure)
         (X (lambda (arg) ((procedure procedure) arg))))))
    (lambda (func-arg)
      (lambda (l)
        (if (null? l)
            'no-list
            (if (null? (cdr l))
                (car l)
                (max (car l) (func-arg (cdr l))))))))
   '(4 5 6 3 4 8 6 2))
作为留给有兴趣的同学们的作业,写一个没有过程名字"max"的findmax。你认为可以除掉findmax里剩下的名字中的多少个?讨论一个没有名字的社会吧……


另:如果对上面提到的一些名字词如组合子等等不理解的话,可以看看这个帖子
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值