匿名函数的递归(1)

首先我们定义一个函数length:

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

这个函数很简单,只有一个参数。功能就是去求参数lat(一个列表)的长度。

下面是调用示例:

> (length '(2 3 4))
3

这个函数并没有什么神奇之处,让我们思考下面两个问题:

1、递归是什么?

这个问题很好回答,简单来说就是函数自己调用自己。

2、匿名函数如何使用递归???

这个问题嘛......。上面例子中,我们给函数取了个名字length,在递归调用的时候直接就是用length来代替函数自身。如果我们的函数没有名字呢,如何使用递归?

我们先定义一个叫做infinity的函数:

(define infinity
  (lambda()
    (infinity)))

这个函数一但调用就出不来了,一直到堆栈溢出。这个函数有什么用?先往下看!

定义一个匿名函数:

(lambda (lat)
   (cond
     ((null? lat) 0)
     (else(infinity))))

这个匿名函数需要一个参数lat。如果列表lat为空,返回0。否则就执行infinity函数。我们可知道infinity是个无底洞。所以调用infinity的潜在意义是绝对不能运行到这里。那这个函数又有什么用呢?让我们调用一下:

((lambda (lat)
   (cond
     ((null? lat) 0)
     (else(infinity)))) '())

结果返回 : 0

((lambda (lat)
   (cond
     ((null? lat) 0)
     (else(infinity)))) '(1))

函数无法返回,直到堆栈溢出,因为执行了infinity。所以这个匿名函数的唯一作用就是 : 测量0个元素的列表长度。我们把这个匿名函数先叫做 length-0 (只能测量包含0个元素列表的函数)

我们再来定义一个匿名函数:


(lambda (lat)
  (cond
    ;;如果列表为空,返回0
    ((null? lat) 0)
    (else
     ;;否则返回 1 + (length-0 对列表剩下值的操作)
     (+ 1 ((lambda(lat)
            (cond
              ((null? lat) 0)
              (else
               (infinity)))) (cdr lat))))))
这个函数比length-0复杂了一些,我们发现它可以测量最多包含一个元素的列表长度。如果传入大于一个元素的列表,函数将会执行到infinity。所以,我们称之为length-1


照着这个思路我们来定义length-2:


(lambda (lat)
  (cond
    ;;如果列表为空,返回0
    ((null? lat) 0)
    (else
     ;;否则返回 1 + (length-1 对列表剩下值的操作)
     (+ 1 ((lambda (lat)
            (cond
              ;;如果列表为空,返回0
              ((null? lat) 0)
              (else
               ;;否则返回 1 + (length-0 对列表剩下值的操作)
               (+ 1 ((lambda(lat)
                       (cond
                         ((null? lat) 0)
                         (else
                          (infinity)))) (cdr lat)))))) (cdr lat))))))
这个函数看着复杂,其实思路很固定,依次调用length-1 和 length-0。这个函数只能计算出不多于两个元素的列表长度。


通过上面三个列子的思路,我们就可以定义一个计算列表长度的函数了,前提是必须知道传入列表的最大长度。总之随着支持列表长度的增加,函数的体积变得越来越庞大,并且难以阅读。让我们观察一下length-2,看看有没有什么发现:

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

我们发现标记颜色的部分是一模一样的,说明这些代码虽然很复杂,但是模式确实一样的。这样的代码我们可以用什么技术来着?当然是抽象了。

更近一步我们发现,上面的变化就是下面xxx的变化,从length-1 length-0 到 infinity(这个绝不定调用)。

(lambda(lat)
  (cond
    ((null? lat) 0)
    (else
     (+ 1 xxx (cdr lat)))))

我们先做第一次抽象尝试,我们不妨叫做length-length:

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

这里的length代表的是一个函数,也就是上面的xxx。我们将之抽象出来。面向对象编程中,抽象一个东西是将之封装成一个类。而函数编程中,抽象就是将之抽象成一个单独的函数。上面我们得到的length-length是一个高阶函数,我们传入一个函数,返回值还是一个函数。

我们用length-length重新定义length-0


((lambda(length)
  (lambda(lat)
  (cond
    ((null? lat) 0)
    (else
     (+ 1 (length (cdr lat)))))))
 infinity)
我们调用了length-length ,参数是infinity。我们展开表达式看一下:



;;length-length infinity 展开后的表达式 length-length-0
(lambda(lat)
  (cond
    ((null? lat) 0)
    (else
     (+ 1 (infinity (cdr lat))))))

;;之前定义的length-0
(lambda (lat)
   (cond
     ((null? lat) 0)
     (else(infinity))))


有人说这个length-0不一样,别忘了。infinity是绝对不能调用的,这两个代码功能上是没有任何区别的。当然我们最早也可以把length-0定义成 length-length-0这种形式。

现在再来用length-length定义length-1

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

就是把length-length-0传递给length-length。

再来重新定义length-2,大家猜一下就知道把length-1传递给length-length

我们得到length-length-2如下

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

看上去虽然复杂,但是很有规律性在里面。我们继续观察length-length-2,发现依然存在大量的重复代码:

((lambda(length)
   (lambda(lat)
     (cond
       ((null? lat) 0)       --------> length-length-2的最外层 length-length
       (else
        (+ 1 (length (cdr lat)))))))

((lambda(length)
    (lambda(lat)
      (cond                  ---------->length-length-1 的最外层 length-length
        ((null? lat) 0)
        (else
         (+ 1 (length (cdr lat)))))))
 
  ((lambda(length)
    (lambda(lat)
      (cond                ----------->length-length-0的最外层length-length
        ((null? lat) 0)
        (else              
         (+ 1 (length (cdr lat))))))) infinity)))

我们把length-length-2稍微改造了一下,就是将length-0恢复成展开前的模式,这样看起来更明显。标记颜色的代码一模一样。呵呵,我们牢记DRY这个原则,所以又到了抽象时刻。还记的函数式编程中如何抽象吗?我们继续将相同的部分抽象成函数。公有的部分正是length-length。

我们重新定义一下length-0,这已经是第三次抽象了。第二次是length-length-0。第三次抽像我们把它叫做 mk-length-0

((lambda(mk-length)
   (lambda(lat)             ----------> 将length-length替换mk-length 并展开,我们得到length-length-0
     ((mk-length infinity) lat)))
 (lambda(length)
   (lambda(lat)
     (cond                 --------------->length-length (我们单独抽出来了)
       ((null? lat) 0)
       (else
        (+ 1 (length (cdr lat))))))))

下面稍微有点绕口,但是并不复杂。注意上面绿色的代码,我们将mk-length作用到infinity上,我们得到length-length-0。这里的mk-length就是length-length。还记得我们如何得到length-length-1 的吗。就是将length-length 作用到length-length-0上。所以我们将mk-length 作用到length-length-0上就能得到length-length-1 。也就是说 将mk-length 作用到(mk-length infinity) 上我们得到length-length-1.

ok,有了上面的分析,我们来重新定义length-1,叫做mk-length-1。

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

和mk-length-0相比,mk-length-1 只有上面标记黄色的地方发生变化,其他都不变。这次抽象不错,终于有点样子啦。

同理,重新定义length-2,称为mk-length-2

((lambda(mk-length)
   (lambda(lat)
     ((mk-length
       (mk-length         ----------->唯一变化的地方
        (mk-length infinity))) lat)))
 (lambda(length)
   (lambda(lat)
     (cond
       ((null? lat) 0)
       (else
        (+ 1 (length (cdr lat))))))))

定义mk-length-n ? so easy !!

((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=100000,这个函数估计没有人愿意写吧,还得继续朝目标努力啊。什么目标来着?使用递归啊。





你可能感兴趣的:(递归,lisp,schemer,Y算子)