匿名函数的递归(2)

先回顾一下上篇的最终的推导成果

对于不多于n个元素的列表,我们可以使用下面的匿名函数来进行计算。虽然有点那么个意思,但是离递归还有很大的距离呢。

((lambda(mk-length)
   (lambda(lat)
     ((mk-length
       (mk-length            ------->总共 n 个 mk-length
        ......  
        (mk-length infinity))...) lat)))
 (lambda(length)
   (lambda(lat)
     (cond
       ((null? lat) 0)
       (else
        (+ 1 (length (cdr lat))))))))

如果n为0,我们就得到了mk-length-0

((lambda(mk-length)
  (mk-length infinity))
 (lambda(length)
   (lambda(lat)
     (cond
       ((null? lat) 0)
       (else
        (+ 1 (length (cdr lat))))))))

从上篇我们知道,infinity的含义就是绝对不要调用,所以其实infinity是什么函数对我们没有任何影响。也就是说我们可以随意的去替换它。

我们用mk-length 替换infinity

((lambda(mk-length)
  (mk-length mk-length))   ------>  将infinity 替换成了mk-length
 (lambda(length)
   (lambda(lat)
     (cond                       ------->  length-length 函数,最终要替换mk-length形参
       ((null? lat) 0)
       (else
        (+ 1 (length (cdr lat))))))))

我们代入length-length,然后展开得到下面代码:

 ((lambda(length)
   (lambda(lat)
     (cond               ---------->   length-length 
       ((null? lat) 0)
       (else
        (+ 1 (length (cdr lat)))))))
 (lambda(length)
   (lambda(lat)
     (cond             ---------->   length-length 
       ((null? lat) 0)         
       (else
        (+ 1 (length (cdr lat))))))))

我们接着将length-length 代入length-length,继续展开
  (lambda(lat)
    (cond
      ((null? lat) 0)
      (else
       (+ 1 ((lambda(length)
               (lambda(lat)
                 (cond             ----------------> 用length-length 代换了length形参。
                   ((null? lat) 0)
                   (else
                    (+ 1 (length (cdr lat)))))))      
             (cdr lat))))))

我们看一下最终展开后函数的逻辑。如果列表为空返回0,否则用返回 1 + (length-length (cdr lat))

我们知道length-length是个高阶函数,它的参数是一个函数,返回的是一个以列表为参数的另一个函数。所以(length-length (cdr lat))肯定会报错。所以这个函数和length-0是等价的。这也验证了我们用mk-length替换infinity对函数的含义是没有任何影响。

这样替换有什么意义呢?我们在考虑一下infinity 的含义。我们知道这个函数对我们计算列表长度是没有任何作用的,我们只有在到达这个这个函数之前结束列表长度计算才能正确的得到列表长度。 我们再来看一下上面将  length-length 代入length-length后的结果:得到的就是一个只能计算包含0个元素列表长度的函数。如果我们函数走到infinity位置的时候都能生成一个新的length-0函数,那么不就可以无限的调用下去了吗?我们不就得到计算任意长度列表的函数了吗?

我们回到mk-length-0展开前的状态

((lambda(mk-length)
  (mk-length mk-length))   ------>  以将infinity 替换成了mk-length
 (lambda(mk-length)   ----->我们改变了形参名字,这个对函数没有任何影响
   (lambda(lat)
     (cond                       ------->  length-length 函数,最终要替换mk-length形参
       ((null? lat) 0)
       (else
        (+ 1 (mk-length(cdr lat))))))))   

  注意上面标记绿色的地方。我们现在要做的就是一旦函数走到那里,我们就得到一个新的只能计算0元素列表长度的函数 。这样函数就能无限的走先去,直到我们计算出列表的长度。上面函数中的mk-length最终会被length-length代换,而(length-length length-length)根据我们上面的展开过程正是一个只能计算出0元素个列表的函数。所以,我们只要把绿色标记的地方换成(mk-length mk-length)就能得到一个计算任意列表长度的函数。代码如下:

((lambda(mk-length)
   (mk-length mk-length))    
 (lambda(mk-length)    
   (lambda(lat)
     (cond                       
       ((null? lat) 0)
       (else
        (+ 1 ((mk-length mk-length)(cdr lat))))))))

这样我们得到了一个能计算出任意列表长度的函数了。但是这个函数一点也不像递归函数啊。我们来变幻一下将(mk-length mk-length)提出来。根据函数式编程经验,一旦抽象出一部分代码,必然要引入一个新的函数。我们得到的新函数如下:

((lambda(mk-length)
   (mk-length mk-length))
 (lambda(mk-length)
   ((lambda (length)  ------------->新抽象出来的函数
      (lambda(lat)
        (cond                       
          ((null? lat) 0)
          (else
           (+ 1 (length(cdr lat)))))))
    (mk-length mk-length))))

看着上面标记颜色的代码,是不是非常像一个真正的递归函数?上面代码我们一旦执行会发现无法返回,直至栈溢出。原因出在提取(绿色部分)出来的(mk-length mk-length)。schemer在计算表达式的时候,会先求出子表达式,然后在计算整个表达式。所以函数会尝试去求(mk-length mk-length)的值,而大家自己代换参数求一下就会发现这会引起无限递归。这里有个小技巧,也就是“懒计算”的一种用法。我们观察下(mk-length mk-length)返回的是一个只有一个参数的函数(就是上面我们展开后的最终形态)。所以(mk-length mk-length)和下面的表达式是的等价的 :

(lambda(x)
      ((mk-length mk-length) x))

这里体现了lisp中代码就是数据的思想,好好体会吧。这个表达式只有在真正传入参数的时候才会去真正计算。所以不会产生无限递归的问题。我们用这个表达式替换(mk-length mk-length)得到下面代码:

((lambda(mk-length)
   (mk-length mk-length))
 (lambda(mk-length)
   ((lambda (length)
      (lambda(lat)
        (cond                       
          ((null? lat) 0)              -------------> 像真正递归函数的部分
          (else
           (+ 1 (length(cdr lat)))))))
    (lambda(x)                    -------------------->替换了 (mk-length mk-length)
      ((mk-length mk-length) x)))))

现在呢,我们再把像真正递归的部分提取出来。

((lambda(len) 
  ((lambda(mk-length)
     (mk-length mk-length))
   (lambda(mk-length)
     (f(lambda(x)
        ((mk-length mk-length) x))))))
(lambda (length)
      (lambda(lat)
        (cond                              ------------------>变成了参数
          ((null? lat) 0)
          (else
           (+ 1 (length(cdr lat))))))))

现在我们主要看一下上面黄颜色标记的部分。我们发现这部分和真正去求列表长度的逻辑已经完全分开了。绿色标记部分是求列表长度的一个伪递归。为什么是伪递归呢?因为length只是一个匿名函数的参数,是我们无法得到真正的函数名称时引入的一个参数。我们把黄色标记部分单独拿出来并命名为Y:

(define Y
  (lambda(len)
    ((lambda(f)  -------> 为了少敲几个字母把 mk-length 换成 f
       (f f))
     (lambda(f)
       (len(lambda(x)
             ((f f) x)))))))

有了上面这个函数,我们让匿名函数也能递归啦。上面这个表达式有个很有趣的名字Y。这个可不是我随意取的,它就是大名鼎鼎的Y算子。












转载于:https://my.oschina.net/clopopo/blog/141490

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值