不应滥用named let
> (define (f x) x) > (define (g x) (let rec((x x)) x)) > (define a '(1 2 3)) > (f a) (1 2 3) > (eq? a (f a)) #t > (eq? a (g a)) #t > (define b (g a)) > (set-car! b 10) > a (10 2 3) >
可见,g函数的定义中,named let并未深拷贝x的值,它只是建立传入参数的引用而已.
那这也说明,如果一个函数所有参数在递归过程中都会发生改变,那么一般没必要用named let.
又如:
;切割named let版 (1 2 3 4) 2 -> (3 4) (define (tail x n) (let recur ((x x)(n n)) (if (null? x) '() (if (> n 0) (recur (cdr x) (- n 1)) (cons (car x) (recur (cdr x) (- n 1))))))) ;切割原始版 (define (tail x n) (if (null? x) '() (if (> n 0) (tail (cdr x) (- n 1)) (cons (car x) (tail (cdr x) (- n 1))))))
原始版就是更好的.
但是,对于递归过程中函数所有参数都在变化的情形,有两种情况例外,仍然需要named let:
(1)函数要求返回递归过程中的某些变量的最终状态.例如求一个list的元素个数的函数定义:
(define (len x) (let recur ((x x)(y 0)) (if (null? x) y (recur (cdr x) (+ y 1)))))
由于需要一个变量来记录(cdr x)的次数,因此在内嵌函数recur中增加一个参数y来实现是非常合适的.
(2)处理不定参数的情形.这个可以用map来作非常好的说明:
(define (imap f x . y) (if (null? y) (let recur ((x x)) (if (null? x) '() (cons (f (car x)) (recur (cdr x))))) (let recur ((x x) (y y)) (if (null? x) '() (cons (apply f (car x) (imap car y)) (recur (cdr x) (imap cdr y)))))))
显然,结合条件判断,named let能够将复杂情形转化为简单情形.思路是:
如果y是空表,那么imap等于一个只有1个参数recur函数.否则就等于2个参数的recur函数.而后者的参数传递过程中,我们又需要用到1个参数的情形:
(imap car y)
(imap cdr y)
这真是非常精妙的.