不应滥用named let

时间:2023-03-08 20:26:49
> (define (f x) x)
> (define (g x) (let rec((x x)) x))
> (define a '(1 2 3)) > (f a)
( )
> (eq? a (f a))
#t
> (eq? a (g a))
#t > (define b (g a))
> (set-car! b )
> a
( )
>

可见,g函数的定义中,named let并未深拷贝x的值,它只是建立传入参数的引用而已.

那这也说明,如果一个函数所有参数在递归过程中都会发生改变,那么一般没必要用named let.

又如:

;切割named let版 (   )  -> ( )
(define (tail x n)
(let recur ((x x)(n n))
(if (null? x)
'()
(if (> n )
(recur (cdr x) (- n ))
(cons (car x) (recur (cdr x) (- n ))))))) ;切割原始版
(define (tail x n)
(if (null? x)
'()
(if (> n )
(tail (cdr x) (- n ))
(cons (car x) (tail (cdr x) (- n ))))))

原始版就是更好的.

但是,对于递归过程中函数所有参数都在变化的情形,有两种情况例外,仍然需要named let:

(1)函数要求返回递归过程中的某些变量的最终状态.例如求一个list的元素个数的函数定义:

(define (len x)
(let recur ((x x)(y ))
(if (null? x)
y
(recur (cdr x) (+ y )))))

由于需要一个变量来记录(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)

这真是非常精妙的.