目前尚不清楚实质,但已经能够从形式上理解它的某些好处,有个很简单的连乘函数可以说明:
为了展示究竟发生了什么,我包装了下乘法函数,将其变为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)(* 1(* 2(* 3 (break 0)))))) 0 > (call/cc (lambda(break)(* 1(* 2(* 3 (* 4 1)))))) 24
(let ((@ (lambda (x) (display "@") x)) (* (lambda (x) (display "*") x)) (f (lambda (x) x))) ((@ (call/cc f))(* (call/cc f))))