目前尚不清楚实质,但已经能够从形式上理解它的某些好处,有个很简单的连乘函数可以说明:
为了展示究竟发生了什么,我包装了下乘法函数,将其变为mul.
我们将比较product和xproduct的区别.
;包装乘法函数
(define (mul x y)
(display x)
(display " * ")
(display y)
(newline)
(* x y))
;常规版
(define (product ls)
(let f ((ls ls))
(cond
((null? ls) 1)
((= (car ls) 0) 0)
(else (mul (car ls) (f (cdr ls)))))))
;call/cc版
(define xproduct
(lambda (ls)
(call/cc
(lambda (break)
(let f ((ls ls))
(cond
((null? ls) 1)
((= (car ls) 0) (break 0))
(else (mul (car ls) (f (cdr ls))))))))))
;比较
(product '(1 2 3 4 5))
(xproduct '(1 2 3 4 5))
(product '(1 2 3 0 5))
(xproduct '(1 2 3 0 5))
结果:
5 * 1
4 * 5
3 * 20
2 * 60
1 * 120
120
5 * 1
4 * 5
3 * 20
2 * 60
1 * 120
120
3 * 0
2 * 0
1 * 0
0
0
我们可以看到,对不含0的连乘,两个函数的内部运行是一样的.
然而对于包含0的连乘,call/cc的方式就显得比较合理.
product碰到0,即使能够中止递归过程,返回一个0,也仅此而已,它仍然需要笨拙地把这个0和前面已经存在的数进行连乘.
而人脑认为这是愚蠢的,因为只要发现有一个0,那么无论其他数是怎样的,最终结果必然是0,所以直接返回0就可以了.
call/cc正是在模拟人脑这一想法.
下面展示xproduct函数,对于(1 2 3 0)和(1 2 3 4)两种情况,它的函数体分别是怎么展开的:
> (call/cc (lambda(break)(* (* (* (break )))))) > (call/cc (lambda(break)(* (* (* (* ))))))
(let
((@ (lambda (x) (display "@") x))
(* (lambda (x) (display "*") x))
(f (lambda (x) x)))
((@ (call/cc f))(* (call/cc f))))