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